password reset

This commit is contained in:
Faheed 2025-10-20 17:30:08 +03:00
parent 2f02f10c16
commit 7ae8c16db2
23 changed files with 806 additions and 166 deletions

View File

@ -132,24 +132,24 @@ WSGI_APPLICATION = 'NorahUniversity.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql_psycopg2',
# 'NAME': 'norahuniversity',
# 'USER': 'norahuniversity',
# 'PASSWORD': 'norahuniversity',
# 'HOST': '127.0.0.1',
# 'PORT': '5432',
# }
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'haikal_db',
'USER': 'faheed',
'PASSWORD': 'Faheed@215',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
# }
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
@ -178,6 +178,7 @@ ACCOUNT_SIGNUP_FIELDS = ['email*', 'password1*', 'password2*']
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_EMAIL_VERIFICATION = 'none'
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
@ -197,6 +198,10 @@ CRISPY_BS5 = {
'use_css_helpers': True,
}
ACCOUNT_RATE_LIMITS = {
'send_email_confirmation': None, # Disables the limit
}
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/

View File

@ -693,3 +693,7 @@ class StaffUserCreationForm(UserCreationForm):
class ToggleAccountForm(forms.Form):
pass

View File

@ -29,6 +29,9 @@ 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")
def __str__(self):
return f"image for user {self.user}"
class JobPosting(Base):
# Basic Job Information
JOB_TYPES = [
@ -302,6 +305,26 @@ class JobPosting(Base):
def offer_candidates(self):
return self.all_candidates.filter(stage="Offer")
#counts
@property
def all_candidates_count(self):
return self.candidates.annotate(sortable_score=Cast('ai_analysis_data__match_score',output_field=CharField())).order_by('-sortable_score').count()
@property
def screening_candidates_count(self):
return self.all_candidates.filter(stage="Applied").count()
@property
def exam_candidates_count(self):
return self.all_candidates.filter(stage="Exam").count()
@property
def interview_candidates_count(self):
return self.all_candidates.filter(stage="Interview").count()
@property
def offer_candidates_count(self):
return self.all_candidates.filter(stage="Offer").count()
class JobPostingImage(models.Model):
job=models.OneToOneField('JobPosting',on_delete=models.CASCADE,related_name='post_images')
post_image = models.ImageField(upload_to='post/',validators=[validate_image_size])

View File

@ -108,6 +108,7 @@ urlpatterns = [
path('jobs/<slug:slug>/candidates/<int:candidate_pk>/schedule-meeting-page/', views.schedule_meeting_for_candidate, name='schedule_meeting_for_candidate'),
path('jobs/<slug:slug>/candidates/<int:candidate_pk>/delete_meeting_for_candidate/<int:meeting_id>/', views.delete_meeting_for_candidate, name='delete_meeting_for_candidate'),
# users urls
path('user/<int:pk>',views.user_detail,name='user_detail'),
path('user/user_profile_image_update/<int:pk>',views.user_profile_image_update,name='user_profile_image_update'),
@ -115,6 +116,10 @@ urlpatterns = [
path('settings/',views.admin_settings,name='admin_settings'),
path('staff/create',views.create_staff_user,name='create_staff_user'),
path('set_staff_password/<int:pk>/',views.set_staff_password,name='set_staff_password'),
path('account_toggle_status/<int:pk>',views.account_toggle_status,name='account_toggle_status'),
# Meeting Comments URLs
path('meetings/<slug:slug>/comments/add/', views.add_meeting_comment, name='add_meeting_comment'),

View File

@ -26,8 +26,10 @@ from .forms import (
BreakTimeFormSet,
JobPostingImageForm,
ProfileImageUploadForm,
StaffUserCreationForm
,MeetingCommentForm
StaffUserCreationForm,
MeetingCommentForm,
ToggleAccountForm,
)
from easyaudit.models import CRUDEvent, LoginEvent, RequestEvent
from rest_framework import viewsets
@ -1940,37 +1942,51 @@ def schedule_meeting_for_candidate(request, slug, candidate_pk):
from django.core.exceptions import ObjectDoesNotExist
def user_profile_image_update(request, pk):
user = get_object_or_404(User, pk=pk)
try:
instance =user.profile
except ObjectDoesNotExist as e:
Profile.objects.create(user=user)
user = get_object_or_404(User, pk=pk)
# 2. Ensure Profile exists and get the instance
try:
profile_instance = user.profile
except ObjectDoesNotExist:
profile_instance = Profile.objects.create(user=user)
if request.method == 'POST':
profile_form = ProfileImageUploadForm(request.POST, request.FILES, instance=user.profile)
profile_form = ProfileImageUploadForm(
request.POST,
request.FILES,
instance=profile_instance # <--- USE profile_instance HERE
)
if profile_form.is_valid():
profile_form.save()
messages.success(request, 'Image uploaded successfully')
messages.success(request, 'Image uploaded successfully.')
return redirect('user_detail', pk=user.pk)
else:
messages.error(request, 'An error occurred while uploading the image')
messages.error(request, 'An error occurred while uploading the image. Please check the errors below.')
else:
profile_form = ProfileImageUploadForm(instance=user.profile)
#
profile_form = ProfileImageUploadForm(instance=profile_instance)
context = {
'profile_form': profile_form,
'user': user,
}
return render(request, 'user/profile.html', context)
def user_detail(request, pk):
user = get_object_or_404(User, pk=pk)
profile_form = ProfileImageUploadForm()
try:
profile_instance = user.profile
profile_form = ProfileImageUploadForm(instance=profile_instance)
except:
profile_form = ProfileImageUploadForm()
if request.method == 'POST':
first_name=request.POST.get('first_name')
last_name=request.POST.get('last_name')
@ -2068,15 +2084,17 @@ def create_staff_user(request):
@user_passes_test(is_superuser_check)
def admin_settings(request):
staffs=User.objects.filter(is_superuser=False)
form = ToggleAccountForm()
context={
'staffs':staffs
'staffs':staffs,
'form':form
}
return render(request,'user/admin_settings.html',context)
from django.contrib.auth.forms import SetPasswordForm
@user_passes_test(is_superuser_check)
def set_staff_password(request,pk):
user=get_object_or_404(User,pk=pk)
print(request.POST)
@ -2085,10 +2103,11 @@ def set_staff_password(request,pk):
if form.is_valid():
form.save()
messages.success(request,f'Password successfully changed')
return redirect('admin_settings')
else:
form=SetPasswordForm(user=user)
messages.error(request,f'Password does not match please try again.')
return redirect('set_staff_password',user=user)
return redirect('admin_settings')
else:
form=SetPasswordForm(user=user)
@ -2096,6 +2115,28 @@ def set_staff_password(request,pk):
@user_passes_test(is_superuser_check)
def account_toggle_status(request,pk):
user=get_object_or_404(User,pk=pk)
if request.method=='POST':
print(user.is_active)
form=ToggleAccountForm(request.POST)
if form.is_valid():
if user.is_active:
user.is_active=False
user.save()
messages.success(request,f'Staff with email: {user.email} deactivated successfully')
return redirect('admin_settings')
else:
user.is_active=True
user.save()
messages.success(request,f'Staff with email: {user.email} activated successfully')
return redirect('admin_settings')
else:
messages.error(f'Please correct the error below')
# @login_required
# def user_detail(requests,pk):

View File

@ -0,0 +1,155 @@
{% load static i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% translate "Account Inactive" %} - KAAUH ATS</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
{# Include Font Awesome for icons #}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" crossorigin="anonymous">
<style>
/* -------------------------------------------------------------------------- */
/* CUSTOM TEAL THEME OVERRIDES FOR BOOTSTRAP (Copied from your Sign In page) */
/* -------------------------------------------------------------------------- */
:root {
--bs-primary: #00636e; /* Dark Teal */
--bs-primary-rgb: 0, 99, 110;
--bs-primary-light: #007a88;
--bs-body-bg: #f8f9fa;
--bs-body-color: #212529;
--bs-border-color: #dee2e6;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bs-body-bg);
}
/* Custom Left Panel */
.left-panel {
flex: 1;
background: url("{% static 'image/kaauh_banner.png' %}") no-repeat center center;
background-size: cover;
position: relative;
display: flex;
align-items: flex-end;
padding: 3rem;
color: white;
z-index: 1;
}
.left-panel::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 50%);
z-index: 0;
}
.left-panel-content {
position: relative;
z-index: 2;
}
/* Right Panel Styling */
.right-panel {
background-color: white;
padding: 3rem;
}
.form-fields {
max-height: 100%;
overflow-y: auto;
}
/* Component Overrides to use Teal Theme */
.btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
font-weight: 600;
border-radius: 0.5rem;
box-shadow: 0 4px 8px rgba(0, 99, 110, 0.2);
transition: all 0.2s ease;
}
.btn-primary:hover {
background-color: var(--bs-primary-light);
border-color: var(--bs-primary-light);
box-shadow: 0 6px 10px rgba(0, 99, 110, 0.3);
}
.text-accent {
color: var(--bs-primary) !important;
}
/* ADJUSTED: Custom size adjustment for right panel on desktop */
@media (min-width: 992px) {
.right-panel-col {
flex: 0 0 450px;
}
.right-panel-col > .d-flex {
height: 100%;
justify-content: center;
align-items: center;
padding-top: 0;
padding-bottom: 0;
}
.right-panel-content-wrapper {
max-width: 350px;
width: 100%;
}
}
</style>
</head>
<body>
<div class="d-flex vh-100 w-100">
<div class="left-panel d-none d-lg-flex flex-grow-1">
<div class="left-panel-content">
<h1 class="text-4xl font-weight-bold mb-4" style="font-size: 1.5rem;">
<span class="text-white">
<div class="hospital-text text-center text-md-start me-3">
<div class="ar small">{% translate "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية" %}</div>
<div class="ar small">{% translate "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي" %}</div>
<div class="en small">{% translate "Princess Nourah bint Abdulrahman University" %}</div>
<div class="en small">{% translate "King Abdullah bin Abdulaziz University Hospital" %}</div>
</div>
</span>
</h1>
<small>Powered By TENHAL | تنحل</small>
</div>
</div>
<div class="d-flex flex-column right-panel right-panel-col flex-grow-1 align-items-center justify-content-center">
<div class="right-panel-content-wrapper text-center">
<i class="fas fa-user-slash text-danger mb-4" style="font-size: 3rem;"></i>
<h2 id="form-title" class="h3 fw-bold mb-3 text-danger">{% translate "Account Inactive" %}</h2>
<div class="form-fields">
<p class="lead text-muted mb-4">
{% translate "Access denied. This account has been marked as inactive by an administrator." %}
</p>
<p class="text-muted small">
{% translate "If you believe this is an error, please contact the system administrator for assistance." %}
</p>
<div class="d-grid mt-4">
<a href="{% url 'account_login' %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-sign-in-alt me-2"></i> {% translate "Return to Sign In" %}
</a>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -0,0 +1,125 @@
{% extends "base.html" %}
{% load i18n %}
{% load account %}
{% load crispy_forms_tags %}
{% block title %}{% translate "Email Addresses" %}{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row mb-4">
<div class="col">
<h3 class="fw-bold">{% translate "Account Settings" %}</h3>
<p class="text-muted">{% translate "Manage your personal details and security." %}</p>
</div>
</div>
<div class="row">
{# ------------------- LEFT COLUMN: ACCOUNT MENU (New Card Style) ------------------- #}
<div class="col-md-4 col-lg-3 mb-4">
<div class="card shadow-sm border-0 rounded-4">
<div class="card-body p-0">
<div class="list-group list-group-flush rounded-4">
{# Assuming a main 'Profile' or 'Personal Information' page exists #}
<a href="{% url 'user_detail' user.pk %}" class="list-group-item list-group-item-action border-0 rounded-top-4 py-3">
<i class="fas fa-user-circle me-2"></i> {% translate "Personal Information" %}
</a>
{# Highlight the current page (Email) as active #}
<a href="{% url 'account_email' %}" class="list-group-item list-group-item-action active border-0 py-3" style="background-color: #e6f3f3; color: #008080; font-weight: 500;" aria-current="true">
<i class="fas fa-envelope me-2"></i> {% translate "Email Addresses" %}
</a>
<a href="{% url 'account_change_password' %}" class="list-group-item list-group-item-action border-0 py-3">
<i class="fas fa-lock me-2"></i> {% translate "Change Password" %}
</a>
<a href="{% url 'account_logout' %}" class="list-group-item list-group-item-action border-0 rounded-bottom-4 text-danger py-3">
<i class="fas fa-sign-out-alt me-2"></i> {% translate "Sign Out" %}
</a>
</div>
</div>
</div>
</div>
{# ------------------- RIGHT COLUMN: EMAIL MANAGEMENT ------------------- #}
<div class="col-md-8 col-lg-9">
<div class="card shadow-sm border-0 rounded-4">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">{% translate "Email Addresses" %}</h5>
<p class="text-muted border-bottom pb-3">{% translate "These email addresses are linked to your account. You can set the primary address, resend verification, or remove an address." %}</p>
{% if emailaddresses %}
{% for emailaddress in emailaddresses %}
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-sm-center py-3 {% if not forloop.last %}border-bottom{% endif %}">
<p class="mb-2 mb-sm-0 me-3">
<span class="fw-bold text-dark">{{ emailaddress.email }}</span>
{# Status Badges: Using rounded-pill and appropriate colors #}
{% if emailaddress.primary %}
<span class="badge rounded-pill bg-info text-dark ms-2">{% translate "Primary" %}</span>
{% endif %}
{% if emailaddress.verified %}
<span class="badge rounded-pill bg-success ms-2">{% translate "Verified" %}</span>
{% else %}
<span class="badge rounded-pill bg-warning text-dark ms-2">{% translate "Unverified" %}</span>
{% endif %}
</p>
<div class="d-flex flex-wrap gap-2">
{# 1. MAKE PRIMARY ACTION #}
{% if not emailaddress.primary %}
<form method="post" action="{% url 'account_email' %}" class="d-inline">
{% csrf_token %}
<input type="hidden" name="email" value="{{ emailaddress.email }}" />
<input type="hidden" name="action" value="set_primary" />
<button type="submit" class="btn btn-sm btn-outline-primary">{% translate "Make Primary" %}</button>
</form>
{% endif %}
{# 2. RESEND VERIFICATION ACTION #}
{% if not emailaddress.verified %}
<form method="post" action="{% url 'account_email' %}" class="d-inline">
{% csrf_token %}
<input type="hidden" name="email" value="{{ emailaddress.email }}" />
<input type="hidden" name="action" value="send" />
<button type="submit" class="btn btn-sm btn-outline-warning">{% translate "Re-send Verification" %}</button>
</form>
{% endif %}
{# 3. REMOVE ACTION #}
{% if not emailaddress.primary %}
<form method="post" action="{% url 'account_email' %}" class="d-inline">
{% csrf_token %}
<input type="hidden" name="email" value="{{ emailaddress.email }}" />
<input type="hidden" name="action" value="remove" />
<button type="submit" class="btn btn-sm btn-outline-danger">{% translate "Remove" %}</button>
</form>
{% endif %}
</div>
</div>
{% endfor %}
{% else %}
<p class="alert alert-info mt-3">{% translate "No email addresses found." %}</p>
{% endif %}
<hr class="my-4">
{# ------------------- ADD EMAIL FORM ------------------- #}
<h5 class="fw-bold mb-3">{% translate "Add Email Address" %}</h5>
<form method="post" action="{% url 'account_email' %}">
{% csrf_token %}
{{ form|crispy }}
{# Teal/Dark Green button consistent with "Save Changes" #}
<button class="btn btn-success mt-3" type="submit" style="background-color: #008080; border-color: #008080;">{% translate "Add Email" %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,39 @@
{% load i18n %}
{% load static %}
{% autoescape off %}
<div style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: auto; border: 1px solid #ddd; border-radius: 8px; overflow: hidden;">
<div style="background-color: #00636e; padding: 20px; color: white; text-align: center;">
<h1 style="margin: 0; font-size: 24px;">{% trans "Password Reset Request" %}</h1>
</div>
<div style="padding: 20px;">
<p>{% trans "Hello," %}</p>
<p>{% trans "You are receiving this email because you or someone else has requested a password reset for your account at" %} <strong>{{ current_site.name }}</strong>.</p>
<p style="text-align: center; margin: 30px 0;">
<a href="{{ password_reset_url }}"
style="display: inline-block; padding: 10px 20px; color: white; background-color: #00636e; border-radius: 5px; text-decoration: none; font-weight: bold;">
{% trans "Click Here to Reset Your Password" %}
</a>
</p>
<p>{% trans "This link is only valid for a limited time." %}</p>
<p>{% trans "If you did not request a password reset, please ignore this email. Your password will remain unchanged." %}</p>
<p>
{% trans "Thank you," %}<br>
{% trans "KAAUH ATS Team" %}
</p>
</div>
<div style="background-color: #f4f4f4; padding: 15px; font-size: 12px; color: #777; text-align: center;">
{% trans "If the button above does not work, copy and paste the following link into your browser:" %}<br>
<a href="{{ password_reset_url }}" style="color: #00636e; word-break: break-all;">{{ password_reset_url }}</a>
</div>
</div>
{% endautoescape %}

View File

@ -0,0 +1,71 @@
{% extends "base.html" %}
{% load i18n %}
{% load account %}
{% block title %}{% translate "Confirm Email Address" %}{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row mb-4">
<div class="col">
<h3 class="fw-bold">{% translate "Account Verification" %}</h3>
<p class="text-muted">{% translate "Verify your email to secure your account and unlock full features." %}</p>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow-sm border-0 rounded-4">
<div class="card-body p-5 text-center">
{% with emailaddress.email as email %}
{% if confirmation %}
{# ------------------- CONFIRMATION REQUEST (GET) ------------------- #}
{% user_display confirmation.email_address.user as user_display %}
<i class="fas fa-envelope-open-text mb-4" style="font-size: 3rem; color: #008080;"></i>
<h4 class="fw-bold mb-3">{% translate "Confirm Your Email Address" %}</h4>
<p class="lead text-muted">
{% blocktrans with email as email %}Please confirm that **{{ email }}** is an email address for user **{{ user_display }}**.{% endblocktrans %}
</p>
{# Confirmation Form #}
<form method="post" action="{% url 'account_confirm_email' confirmation.key %}">
{% csrf_token %}
{# Teal/Dark Green button consistent with the UI theme #}
<button class="btn btn-lg mt-4 px-5" type="submit" style="background-color: #008080; border-color: #008080; color: white;">
{% translate "Confirm" %}
</button>
</form>
{% else %}
{# ------------------- CONFIRMATION FAILED (Error) ------------------- #}
<i class="fas fa-exclamation-triangle text-danger mb-4" style="font-size: 3rem;"></i>
<h4 class="fw-bold mb-3 text-danger">{% translate "Invalid Link" %}</h4>
<p class="lead">
{% translate "The email confirmation link has expired or is invalid." %}
</p>
<p class="text-muted">
{% translate "Please request a new verification email from your account settings page." %}
</p>
<a href="{% url 'account_email' %}" class="btn btn-outline-secondary mt-3 px-5">
{% translate "Go to Settings" %}
</a>
{% endif %}
{% endwith %}
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,80 @@
{% extends "base.html" %}
{% load i18n %}
{% load account %}
{% block title %}{% translate "Sign Out" %}{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row mb-4">
<div class="col">
<h3 class="fw-bold">{% translate "Account Settings" %}</h3>
<p class="text-muted">{% translate "Manage your personal details and security." %}</p>
</div>
</div>
<div class="row">
{# ------------------- LEFT COLUMN: ACCOUNT MENU (New Card Style) ------------------- #}
<div class="col-md-4 col-lg-3 mb-4">
<div class="card shadow-sm border-0 rounded-4">
<div class="card-body p-0">
<div class="list-group list-group-flush rounded-4">
{# Assuming a main 'Profile' or 'Personal Information' page exists #}
<a href="{% url 'user_detail' user.pk %}" class="list-group-item list-group-item-action border-0 rounded-top-4 py-3">
<i class="fas fa-user-circle me-2"></i> {% translate "Personal Information" %}
</a>
<a href="{% url 'account_email' %}" class="list-group-item list-group-item-action border-0 py-3">
<i class="fas fa-envelope me-2"></i> {% translate "Email Addresses" %}
</a>
<a href="{% url 'account_change_password' %}" class="list-group-item list-group-item-action border-0 py-3">
<i class="fas fa-lock me-2"></i> {% translate "Change Password" %}
</a>
{# Highlight the current page (Sign Out) as active #}
<a href="{% url 'account_logout' %}" class="list-group-item list-group-item-action active border-0 rounded-bottom-4 py-3" style="background-color: #fce8e8; color: #dc3545; font-weight: 500;" aria-current="true">
<i class="fas fa-sign-out-alt me-2"></i> {% translate "Sign Out" %}
</a>
</div>
</div>
</div>
</div>
{# ------------------- RIGHT COLUMN: LOGOUT CONFIRMATION ------------------- #}
<div class="col-md-8 col-lg-9">
<div class="card shadow-sm border-0 rounded-4">
<div class="card-body p-4 text-center">
<i class="fas fa-sign-out-alt text-danger mb-4" style="font-size: 3rem;"></i>
<h4 class="fw-bold mb-3">{% translate "Confirm Sign Out" %}</h4>
<p class="lead mb-4">{% translate "Are you sure you want to sign out of your account?" %}</p>
<form method="post" action="{% url 'account_logout' %}">
{% csrf_token %}
{% if redirect_field_value %}
<input type="hidden"
name="{{ redirect_field_name }}"
value="{{ redirect_field_value }}" />
{% endif %}
<div class="d-flex justify-content-center gap-3 mt-4">
{# Sign Out button in danger color #}
<button class="btn btn-danger btn-lg px-5" type="submit">
{% translate "Sign Out" %}
</button>
{# Cancel/Go Back button with outline #}
<a class="btn btn-outline-secondary btn-lg px-5" href="{% url 'account_email' %}">
{% translate "Cancel" %}
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -7,7 +7,7 @@
{% endblock %}
{% block content %}
<div class="d-flex vh-80 w-100 justify-content-center align-items-center">
<div class="d-flex vh-80 w-100 justify-content-center align-items-center mt-5">
<div class="form-card">

View File

@ -7,120 +7,39 @@
<title>{% trans "Set New Password" %} - KAAUH ATS</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" crossorigin="anonymous">
{% get_current_language as LANGUAGE_CODE %}
<style>
/* -------------------------------------------------------------------------- */
/* CUSTOM TEAL THEME OVERRIDES FOR BOOTSTRAP (Copied from provided login page) */
/* CUSTOM TEAL THEME OVERRIDES FOR BOOTSTRAP (Consistent with your UI) */
/* -------------------------------------------------------------------------- */
:root {
/* Define TEAL as the primary color for Bootstrap overrides */
--bs-primary: #00636e; /* Dark Teal */
--bs-primary-rgb: 0, 99, 110;
--bs-primary-light: #007a88; /* Lighter Teal for hover */
/* Background and Text Colors */
--bs-body-bg: #f8f9fa; /* Light gray background */
--bs-body-color: #212529; /* Dark text */
/* Utility colors */
--bs-border-color: #dee2e6; /* Bootstrap default border */
--bs-body-bg: #f8f9fa;
--bs-body-color: #212529;
--bs-border-color: #dee2e6;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bs-body-bg);
}
/* Custom Left Panel (Replicating the original look) */
.left-panel {
flex: 1;
/* NOTE: Static image URL is included here. Ensure 'image/kaauh_banner.png' exists in your static files. */
background: url("{% static 'image/kaauh_banner.png' %}") no-repeat center center;
background-size: cover;
position: relative;
display: flex;
align-items: flex-end;
padding: 3rem;
color: white;
z-index: 1;
}
.left-panel::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 50%);
z-index: 0;
}
.left-panel-content {
position: relative;
z-index: 2;
}
/* Right Panel Styling */
.right-panel {
background-color: white;
padding: 3rem;
}
.form-fields {
max-height: 100%;
overflow-y: auto;
}
/* Component Overrides to use Teal Theme */
.btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
font-weight: 600;
border-radius: 0.5rem;
box-shadow: 0 4px 8px rgba(0, 99, 110, 0.2);
transition: all 0.2s ease;
}
.btn-primary:hover {
background-color: var(--bs-primary-light);
border-color: var(--bs-primary-light);
box-shadow: 0 6px 10px rgba(0, 99, 110, 0.3);
}
.form-control:focus {
border-color: var(--bs-primary);
box-shadow: 0 0 0 0.25rem rgba(0, 99, 110, 0.25);
}
.text-accent {
color: var(--bs-primary) !important;
}
.text-accent:hover {
color: var(--bs-primary-light) !important;
text-decoration: underline;
}
.alert-info-custom {
background-color: #f0f8ff; /* Very light blue */
border-left: 5px solid var(--bs-primary);
color: var(--bs-primary);
}
/* ADJUSTED: Custom size adjustment for right panel on desktop */
body { font-family: 'Inter', sans-serif; background-color: var(--bs-body-bg); }
.left-panel { flex: 1; background: url("{% static 'image/kaauh_banner.png' %}") no-repeat center center; background-size: cover; position: relative; display: flex; align-items: flex-end; padding: 3rem; color: white; z-index: 1; }
.left-panel::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 50%); z-index: 0; }
.left-panel-content { position: relative; z-index: 2; }
.right-panel { background-color: white; padding: 3rem; }
.form-fields { max-height: 100%; overflow-y: auto; }
.btn-primary { background-color: var(--bs-primary); border-color: var(--bs-primary); font-weight: 600; border-radius: 0.5rem; box-shadow: 0 4px 8px rgba(0, 99, 110, 0.2); transition: all 0.2s ease; }
.btn-primary:hover { background-color: var(--bs-primary-light); border-color: var(--bs-primary-light); box-shadow: 0 6px 10px rgba(0, 99, 110, 0.3); }
.form-control:focus { border-color: var(--bs-primary); box-shadow: 0 0 0 0.25rem rgba(0, 99, 110, 0.25); }
.text-accent { color: var(--bs-primary) !important; }
.text-accent:hover { color: var(--bs-primary-light) !important; text-decoration: underline; }
.alert-info-custom { background-color: #f0f8ff; border-left: 5px solid var(--bs-primary); color: var(--bs-primary); }
@media (min-width: 992px) {
/* 1. Set a NARROWER fixed width for the right panel container */
.right-panel-col {
flex: 0 0 450px; /* Width of the panel */
}
/* 2. Vertically and horizontally center the content within the narrow panel */
.right-panel-col > .d-flex {
height: 100%;
justify-content: center;
align-items: center;
padding-top: 0;
padding-bottom: 0;
}
/* 3. Ensure the form container doesn't exceed a smaller size and is centered */
.right-panel-content-wrapper {
max-width: 350px; /* Max width of the form elements inside the panel */
width: 100%;
}
.right-panel-col { flex: 0 0 450px; }
.right-panel-col > .d-flex { height: 100%; justify-content: center; align-items: center; padding-top: 0; padding-bottom: 0; }
.right-panel-content-wrapper { max-width: 350px; width: 100%; }
}
</style>
</head>
@ -153,14 +72,17 @@
<div class="form-fields">
{% if form %}
<p class="text-muted small mb-1 text-center">
{% trans 'Please enter your new password below.' %}
</p>
<p class="text-muted small mb-4 text-center">
{% trans 'Please enter your new password below. You can then log in.' %}
{% trans 'You can then log in.' %}
</p>
<form method="post" action="{{ action_url }}">
<form method="post" action=".">
{% csrf_token %}
{# Display any general form errors #}
{# Non-Field Errors (General errors like tokens or passwords not matching) #}
{% if form.non_field_errors %}
<div class="alert alert-danger" role="alert">
{% for error in form.non_field_errors %}
@ -173,9 +95,12 @@
<div class="mb-3">
<label for="{{ form.password.id_for_label }}" class="form-label fw-semibold">{% trans "New Password" %} *</label>
{# **CRITICAL FIX:** Iterate over the errors to display them correctly #}
{% if form.password.errors %}
<div class="alert alert-danger p-2 small">
{{ form.password.errors }}
{% for error in form.password.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
@ -187,9 +112,12 @@
<div class="mb-3">
<label for="{{ form.password2.id_for_label }}" class="form-label fw-semibold">{% trans "Confirm New Password" %} *</label>
{# **CRITICAL FIX:** Iterate over the errors to display them correctly #}
{% if form.password2.errors %}
<div class="alert alert-danger p-2 small">
{{ form.password2.errors }}
{% for error in form.password2.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
@ -197,12 +125,12 @@
class="form-control" placeholder="{% trans 'Confirm new password' %}" required>
</div>
{# Hidden fields, if any (like uidb64, token/key) #}
{# Hidden fields MUST be present for the POST request to be valid #}
{{ form.uid }}
{{ form.token }}
{# Submit Button #}
<button type="submit" name="action" class="btn btn-primary w-100 mt-4">
<button type="submit" class="btn btn-primary w-100 mt-4">
{% trans "Change Password" %}
</button>
</form>

View File

@ -0,0 +1,87 @@
{% load static i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans "Password Changed" %} - KAAUH ATS</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" crossorigin="anonymous">
{% get_current_language as LANGUAGE_CODE %}
<style>
/* -------------------------------------------------------------------------- */
/* CUSTOM TEAL THEME OVERRIDES (Consistent with your UI) */
/* -------------------------------------------------------------------------- */
:root {
--bs-primary: #00636e; /* Dark Teal */
--bs-primary-rgb: 0, 99, 110;
--bs-primary-light: #007a88; /* Lighter Teal for hover */
--bs-body-bg: #f8f9fa;
--bs-body-color: #212529;
}
body { font-family: 'Inter', sans-serif; background-color: var(--bs-body-bg); }
.left-panel { flex: 1; background: url("{% static 'image/kaauh_banner.png' %}") no-repeat center center; background-size: cover; position: relative; display: flex; align-items: flex-end; padding: 3rem; color: white; z-index: 1; }
.left-panel::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 50%); z-index: 0; }
.left-panel-content { position: relative; z-index: 2; }
.right-panel { background-color: white; padding: 3rem; }
.btn-primary { background-color: var(--bs-primary); border-color: var(--bs-primary); font-weight: 600; border-radius: 0.5rem; box-shadow: 0 4px 8px rgba(0, 99, 110, 0.2); transition: all 0.2s ease; }
.btn-primary:hover { background-color: var(--bs-primary-light); border-color: var(--bs-primary-light); box-shadow: 0 6px 10px rgba(0, 99, 110, 0.3); }
.text-accent { color: var(--bs-primary) !important; }
.text-success-accent { color: #28a745 !important; } /* Standard green for success */
@media (min-width: 992px) {
.right-panel-col { flex: 0 0 450px; }
.right-panel-col > .d-flex { height: 100%; justify-content: center; align-items: center; padding-top: 0; padding-bottom: 0; }
.right-panel-content-wrapper { max-width: 350px; width: 100%; }
}
</style>
</head>
<body>
<div class="d-flex vh-100 w-100">
<div class="left-panel d-none d-lg-flex flex-grow-1">
<div class="left-panel-content">
<h1 class="text-4xl font-weight-bold mb-4" style="font-size: 1.5rem;">
<span class="text-white">
<div class="hospital-text text-center text-md-start me-3">
<div class="ar small">{% trans "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية" %}</div>
<div class="ar small">{% trans "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي" %}</div>
<div class="en small">{% trans "Princess Nourah bint Abdulrahman University" %}</div>
<div class="en small">{% trans "King Abdullah bin Abdulaziz University Hospital" %}</div>
</div>
</span>
</h1>
<small>Powered By TENHAL | تنحل</small>
</div>
</div>
<div class="d-flex flex-column right-panel right-panel-col flex-grow-1 align-items-center justify-content-center">
<div class="right-panel-content-wrapper text-center">
<i class="fas fa-check-circle text-success-accent mb-4" style="font-size: 3rem;"></i>
<h2 class="h3 fw-bold mb-3 text-accent">{% trans "Password Changed Successfully" %}</h2>
<p class="text-muted mb-4">
{% trans "Your password has been set. You can now use your new password to sign in." %}
</p>
<div class="d-grid mt-4">
<a href="{% url 'account_login' %}" class="btn btn-primary btn-lg">
<i class="fas fa-sign-in-alt me-2"></i> {% trans "Go to Sign In" %}
</a>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -16,7 +16,9 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
{% endif %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="{% static 'css/main.css' %}">
@ -150,7 +152,7 @@
{% if request.user.is_superuser %}
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="{% url 'admin_settings' %}"><i class="fas fa-cog me-3 text-primary fs-5"></i> <span>{% trans "Settings" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="{% url 'easy_logs' %}"><i class="fas fa-history me-3 text-primary fs-5"></i> <span>{% trans "Activity Log" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-question-circle me-3 text-primary fs-5"></i> <span>{% trans "Help & Support" %}</span></a></li>
{% comment %} <li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-question-circle me-3 text-primary fs-5"></i> <span>{% trans "Help & Support" %}</span></a></li> {% endcomment %}
{% endif %}
{% endif %}

View File

@ -275,7 +275,7 @@
{% elif active_tab == 'request' %}
<tr>
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
<td>{{ log.user.get_full_name|default:log.user.username|default:"Anonymous" }}</td>
<td>{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td>
<td>
<span class="badge rounded-pill badge-request-method">{{ log.method }}</span>
</td>

View File

@ -326,11 +326,11 @@
</td>
{# CANDIDATE MANAGEMENT DATA - URLS NEUTRALIZED #}
<td class="candidate-data-cell text-primary-theme"><a href="#" class="text-primary-theme">{% if job.applying_count %}{{ job.applying_count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-info"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-info">{% if job.screening_count %}{{ job.screening_count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-success">{% if job.exam_count %}{{ job.exam_count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_exam_view' job.slug %}" class="text-success">{% if job.interview_count %}{{ job.interview_count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_interview_view' job.slug %}" class="text-success">{% if job.offer_count %}{{ job.offer_count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-primary-theme"><a href="#" class="text-primary-theme">{% if job.all_candidates_count %}{{job.all_candidates_count}}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-info"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-info">{% if job.screening_candidates_count %}{{ job.screening_candidates_count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-success">{% if job.exam_candidates_count %}{{ job.exam_candidates_count}}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_exam_view' job.slug %}" class="text-success">{% if job.interview_candidates_count %}{{ job.interview_candidates_count}}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_interview_view' job.slug %}" class="text-success">{% if job.offer_candidates_count%}{{ job.offer_candidates_count }}{% else %}-{% endif %}</a></td>
</tr>
{% endfor %}
</tbody>

View File

@ -102,6 +102,19 @@
color: white;
}
button i.fa-solid {
/* Re-enable display if it was hidden */
display: inline-block;
/* Ensure the correct font family is applied */
font-family: "Font Awesome 6 Free";
/* Inherit button color */
color: inherit;
}
.text-primary{
color:var(--kaauh-teal) !important;
}
</style>
{% endblock %}
@ -175,20 +188,44 @@
<td class="text-center">
<div class="action-btns">
{# 1. Edit Button (Pencil Icon) #}
<a href="#" class="btn btn-sm btn-outline-secondary" title="{% trans 'Edit User' %}">
<i class="fas fa-edit"></i>
</a>
{# 2. Change Password Button (Key Icon) #}
<a href="{% url 'set_staff_password' user.pk %}" class="btn btn-sm btn-outline-info" title="{% trans 'Change Password' %}">
<i class="fas fa-key"></i>
{% trans 'Change Password' %}
</a>
{# 3. Delete Button (Trash Icon) #}
<a href="#" class="btn btn-sm btn-outline-danger" title="{% trans 'Delete User' %}">
<i class="fas fa-trash-alt"></i>
</a>
{% if user.is_active %}
<form method="post" action="{% url 'account_toggle_status' user.pk %}">
{% csrf_token %}
{# The button for DEACTIVATION #}
<button type="submit" >
<i class="fas fa-times-circle text-danger"></i> Deactivate
</button>
</form>
{% else %}
<form method="post" action="{% url 'account_toggle_status' user.pk %}">
{% csrf_token %}
{# The button for REACTIVATION #}
<button type="submit bg-primary" >
<i class="fas fa-check-circle text-primary"></i> Activate
</button>
</form>
{% endif %}
</div>
</td>
</tr>

View File

@ -191,9 +191,11 @@
</div>
</div>
<!--modal for image upload-->
<!--modal class for image upload-->
<div class="modal fade mt-4" id="myModalForm" tabindex="-1" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@ -204,14 +206,51 @@
<div class="modal-body">
<form method="post" action="{% url 'user_profile_image_update' user.pk %}" enctype="multipart/form-data" >
{% csrf_token %}
{{ profile_form.as_p}}
{% if image_upload_form.instance.post_image %}
<p>Current Image:</p>
<img src="{{ image_upload_form.instance.post_image.url }}" alt="Post Image" style="max-width: 200px;">
<div class="mb-3">
<label for="{{ profile_form.profile_image.id_for_label }}" class="form-label">Profile Image</label>
{# 1. Check if an image currently exists on the bound instance #}
{% if profile_form.instance.profile_image %}
<div class="mb-2">
<small class="text-muted d-block">Current Image:</small>
{# Display Link to View Current Image #}
<a href="{{ profile_form.instance.profile_image.url }}" target="_blank" class="d-inline-block me-3 text-info fw-bold">
View/Download ({{ profile_form.instance.profile_image.name }})
</a>
{# Image Preview #}
<div class="mt-2">
<img src="{{ profile_form.instance.profile_image.url }}"
alt="Profile Image"
style="max-width: 150px; height: auto; border: 1px solid #ccc; border-radius: 4px;">
</div>
</div>
{# 2. Explicitly render the 'Clear' checkbox and the Change input #}
<div class="form-check mt-3">
{# The ClearableFileInput widget renders itself here. It provides the "Clear" checkbox and the "Change" input field. #}
{{ profile_form.profile_image }}
</div>
{% else %}
{# If no image exists, just render the file input for upload #}
<div class="form-control p-0 border-0">
{{ profile_form.profile_image }}
</div>
{% endif %}
<div class="modal-footer mt-2">
<button type="button" class="btn btn-lg btn-secondary" data-bs-dismiss="modal">Close</button>
{# Display any validation errors #}
{% for error in profile_form.profile_image.errors %}
<div class="text-danger small mt-1">{{ error }}</div>
{% endfor %}
</div>
<div class="modal-footer mt-4">
<button type="button" class="btn btn-lg btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
@ -219,5 +258,4 @@
</div>
</div>
</div>
{% endblock %}