update and more fixes

This commit is contained in:
ismail 2025-12-12 22:49:20 +03:00
parent bb430cf049
commit e9c76dfe18
21 changed files with 261 additions and 101 deletions

6
.env
View File

@ -1,3 +1,3 @@
DB_NAME=haikal_db DB_NAME=norahuniversity
DB_USER=faheed DB_USER=norahuniversity
DB_PASSWORD=Faheed@215 DB_PASSWORD=norahuniversity

View File

@ -208,7 +208,8 @@ ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
ACCOUNT_FORMS = {"signup": "recruitment.forms.StaffSignupForm"} ACCOUNT_FORMS = {"signup": "recruitment.forms.StaffSignupForm"}
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" # EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
EMAIL_HOST = "10.10.1.110" EMAIL_HOST = "10.10.1.110"
EMAIL_PORT = 2225 EMAIL_PORT = 2225

View File

@ -58,14 +58,12 @@ class SourceForm(forms.ModelForm):
"name": forms.TextInput( "name": forms.TextInput(
attrs={ attrs={
"class": "form-control", "class": "form-control",
"placeholder": "e.g., ATS System, ERP Integration",
"required": True, "required": True,
} }
), ),
"source_type": forms.TextInput( "source_type": forms.TextInput(
attrs={ attrs={
"class": "form-control", "class": "form-control",
"placeholder": "e.g., ATS, ERP, API",
"required": True, "required": True,
} }
), ),
@ -73,16 +71,15 @@ class SourceForm(forms.ModelForm):
attrs={ attrs={
"class": "form-control", "class": "form-control",
"rows": 3, "rows": 3,
"placeholder": "Brief description of the source system",
} }
), ),
"ip_address": forms.TextInput( "ip_address": forms.TextInput(
attrs={"class": "form-control", "placeholder": "192.168.1.100", attrs={"class": "form-control",
"required":True}, "required":True},
), ),
"trusted_ips":forms.TextInput( "trusted_ips":forms.TextInput(
attrs={"class": "form-control", "placeholder": "192.168.1.100","required": False} attrs={"class": "form-control", "required": False}
), ),
"is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}),
} }
@ -1930,6 +1927,52 @@ class ScheduledInterviewForm(forms.Form):
raise forms.ValidationError(_('Start time cannot be in the past.')) raise forms.ValidationError(_('Start time cannot be in the past.'))
return start_time return start_time
class OnsiteScheduleInterviewUpdateForm(forms.Form):
topic = forms.CharField(
max_length=255,
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'e.g., Interview Topic'
}),
label=_('Interview Topic')
)
start_time = forms.DateTimeField(
widget=forms.DateTimeInput(attrs={
'class': 'form-control',
'type': 'datetime-local',
'required': True
}),
label=_('Start Time')
)
duration = forms.IntegerField(
min_value=1,
required=False,
widget=forms.NumberInput(attrs={
'class': 'form-control',
'placeholder': 'Duration in minutes'
}),
label=_('Duration (minutes)')
)
physical_address = forms.CharField(
max_length=255,
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Physical address'
}),
label=_('Physical Address')
)
room_number = forms.CharField(
max_length=50,
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Room number'
}),
label=_('Room Number')
)
class ScheduledInterviewUpdateStatusForm(forms.Form): class ScheduledInterviewUpdateStatusForm(forms.Form):
status = forms.ChoiceField( status = forms.ChoiceField(
choices=ScheduledInterview.InterviewStatus.choices, choices=ScheduledInterview.InterviewStatus.choices,

View File

@ -1,4 +1,4 @@
# Generated by Django 5.2.7 on 2025-12-11 14:18 # Generated by Django 6.0 on 2025-12-12 11:17
import django.contrib.auth.models import django.contrib.auth.models
import django.contrib.auth.validators import django.contrib.auth.validators

View File

@ -0,0 +1,23 @@
# Generated by Django 6.0 on 2025-12-12 11:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='source',
name='name',
field=models.CharField(help_text='Name of the source', max_length=100, unique=True, verbose_name='Source Name'),
),
migrations.AlterField(
model_name='source',
name='source_type',
field=models.CharField(help_text='Type of the source', max_length=100, verbose_name='Source Type'),
),
]

View File

@ -1748,10 +1748,10 @@ class Source(Base):
max_length=100, max_length=100,
unique=True, unique=True,
verbose_name=_("Source Name"), verbose_name=_("Source Name"),
help_text=_("e.g., ATS, ERP "), help_text=_("Name of the source"),
) )
source_type = models.CharField( source_type = models.CharField(
max_length=100, verbose_name=_("Source Type"), help_text=_("e.g., ATS, ERP ") max_length=100, verbose_name=_("Source Type"), help_text=_("Type of the source")
) )
description = models.TextField( description = models.TextField(
blank=True, blank=True,

View File

@ -680,7 +680,7 @@ def create_interview_and_meeting(schedule_id):
try: try:
schedule = ScheduledInterview.objects.get(pk=schedule_id) schedule = ScheduledInterview.objects.get(pk=schedule_id)
interview = schedule.interview interview = schedule.interview
print("creating zoooooooooooooooooooooooooooooooooooooom meeting")
result = create_zoom_meeting(interview.topic, interview.start_time, interview.duration) result = create_zoom_meeting(interview.topic, interview.start_time, interview.duration)
if result["status"] == "success": if result["status"] == "success":
@ -714,7 +714,6 @@ def handle_zoom_webhook_event(payload):
# Zoom often uses a long 'id' for the scheduled meeting and sometimes a 'uuid'. # Zoom often uses a long 'id' for the scheduled meeting and sometimes a 'uuid'.
# We rely on the unique 'id' that maps to your ZoomMeeting.meeting_id field. # We rely on the unique 'id' that maps to your ZoomMeeting.meeting_id field.
meeting_id_zoom = str(object_data.get('id')) meeting_id_zoom = str(object_data.get('id'))
print(meeting_id_zoom)
if not meeting_id_zoom: if not meeting_id_zoom:
logger.warning(f"Webhook received without a valid Meeting ID: {event_type}") logger.warning(f"Webhook received without a valid Meeting ID: {event_type}")
return False return False

View File

@ -426,6 +426,7 @@ def create_zoom_meeting(topic, start_time, duration):
"Content-Type": "application/json" "Content-Type": "application/json"
} }
ZOOM_MEETING_URL = get_setting('ZOOM_MEETING_URL') ZOOM_MEETING_URL = get_setting('ZOOM_MEETING_URL')
print(ZOOM_MEETING_URL)
response = requests.post( response = requests.post(
ZOOM_MEETING_URL, ZOOM_MEETING_URL,
headers=headers, headers=headers,

View File

@ -187,7 +187,7 @@ class PersonListView(StaffRequiredMixin, ListView, LoginRequiredMixin):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset().select_related("user") queryset = super().get_queryset().select_related("user")
search_query = self.request.GET.get("search", "") search_query = self.request.GET.get("search", "")
print(Person.objects.first().last_name)
if search_query: if search_query:
queryset=queryset.filter( queryset=queryset.filter(
Q(first_name=search_query) | Q(first_name=search_query) |
@ -1910,8 +1910,7 @@ def applications_screening_view(request, slug):
ai_analysis_data__analysis_data_en__screening_stage_rating=screening_rating ai_analysis_data__analysis_data_en__screening_stage_rating=screening_rating
) )
if gpa: if gpa:
applications = applications.filter(person__gpa__gt=gpa) applications = applications.filter(person__gpa__gte=gpa)
print(applications)
if tier1_count > 0: if tier1_count > 0:
applications = applications[:tier1_count] applications = applications[:tier1_count]
@ -2131,27 +2130,43 @@ def applications_document_review_view(request, slug):
@staff_user_required @staff_user_required
def reschedule_meeting_for_application(request, slug): def reschedule_meeting_for_application(request, slug):
from .utils import update_meeting from .utils import update_meeting
from .forms import OnsiteScheduleInterviewUpdateForm
schedule = get_object_or_404(ScheduledInterview, slug=slug) schedule = get_object_or_404(ScheduledInterview, slug=slug)
interview = schedule.interview
if request.method == "POST": if request.method == "POST":
form = ScheduledInterviewForm(request.POST) if interview.location_type == "Remote":
form = ScheduledInterviewForm(request.POST)
else:
form = OnsiteScheduleInterviewUpdateForm(request.POST)
if form.is_valid(): if form.is_valid():
topic = form.cleaned_data.get("topic") topic = form.cleaned_data.get("topic")
start_time = form.cleaned_data.get("start_time") start_time = form.cleaned_data.get("start_time")
duration = form.cleaned_data.get("duration") duration = form.cleaned_data.get("duration")
updated_data = { physical_address = form.cleaned_data.get("physical_address")
"topic": topic, room_number = form.cleaned_data.get("room_number")
"start_time": start_time.isoformat() + "Z", if interview.location_type == "Remote":
"duration": duration, updated_data = {
} "topic": topic,
result = update_meeting(schedule.interview, updated_data) "start_time": start_time.isoformat() + "Z",
"duration": duration,
}
result = update_meeting(schedule.interview, updated_data)
if result["status"] == "success": if result["status"] == "success":
messages.success(request, result["message"]) messages.success(request, result["message"])
else:
messages.error(request, result["message"])
else: else:
messages.error(request, result["message"]) interview.topic = topic
interview.start_time = start_time
interview.duration = duration
interview.room_number = room_number
interview.physical_address = physical_address
interview.save()
messages.success(request, "Meeting updated successfully")
else: else:
print(form.errors)
messages.error(request, "Invalid data submitted.") messages.error(request, "Invalid data submitted.")
return redirect("interview_detail", slug=schedule.slug) return redirect("interview_detail", slug=schedule.slug)
@ -4395,7 +4410,7 @@ def compose_application_email(request, slug):
request=request, request=request,
attachments=None, attachments=None,
async_task_=True, # Changed to False to avoid pickle issues async_task_=True, # Changed to False to avoid pickle issues
from_interview=False, # from_interview=False,
job=job, job=job,
) )
@ -4791,14 +4806,23 @@ def interview_list(request):
@staff_user_required @staff_user_required
def interview_detail(request, slug): def interview_detail(request, slug):
"""View details of a specific interview""" """View details of a specific interview"""
from .forms import ScheduledInterviewUpdateStatusForm from .forms import ScheduledInterviewUpdateStatusForm,OnsiteScheduleInterviewUpdateForm
schedule = get_object_or_404(ScheduledInterview, slug=slug) schedule = get_object_or_404(ScheduledInterview, slug=slug)
interview = schedule.interview interview = schedule.interview
application=schedule.application application=schedule.application
job=schedule.job job=schedule.job
reschedule_form = ScheduledInterviewForm() print(interview.location_type)
if interview.location_type == "Remote":
reschedule_form = ScheduledInterviewForm()
else:
reschedule_form = OnsiteScheduleInterviewUpdateForm()
reschedule_form.initial['physical_address'] = interview.physical_address
reschedule_form.initial['room_number'] = interview.room_number
reschedule_form.initial['topic'] = interview.topic reschedule_form.initial['topic'] = interview.topic
reschedule_form.initial['start_time'] = interview.start_time
reschedule_form.initial['duration'] = interview.duration
meeting=interview meeting=interview
interview_email_form=InterviewEmailForm(job,application,schedule) interview_email_form=InterviewEmailForm(job,application,schedule)
context = { context = {
@ -6109,9 +6133,9 @@ STAGE_CONFIG = {
@login_required @login_required
@staff_user_required @staff_user_required
def export_applications_csv(request, job_slug, stage): def export_applications_csv(request, slug, stage):
"""Export applications for a specific stage as CSV""" """Export applications for a specific stage as CSV"""
job = get_object_or_404(JobPosting, slug=job_slug) job = get_object_or_404(JobPosting, slug=slug)
# Validate stage # Validate stage
if stage not in STAGE_CONFIG: if stage not in STAGE_CONFIG:

4
run.py
View File

@ -227,10 +227,10 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Translate .po files using AI Providers (Z.ai, Ollama, OpenAI)") parser = argparse.ArgumentParser(description="Translate .po files using AI Providers (Z.ai, Ollama, OpenAI)")
parser.add_argument('path', type=str, help='Path to the .po file') parser.add_argument('path', type=str, help='Path to the .po file')
parser.add_argument('--lang', type=str, required=True, help='Target language (e.g., "French", "zh-CN")') parser.add_argument('--lang', type=str, required=True, default='ar', help='Target language (e.g., "French", "zh-CN")')
# Provider Settings # Provider Settings
parser.add_argument('--provider', type=str, default='glm', choices=['glm', 'ollama', 'openai'], help='AI Provider to use') parser.add_argument('--provider', type=str, default='ollama', choices=['glm', 'ollama', 'openai'], help='AI Provider to use')
parser.add_argument('--model', type=str, help='Model name (e.g., glm-4, llama3, gpt-4). Defaults vary by provider.') parser.add_argument('--model', type=str, help='Model name (e.g., glm-4, llama3, gpt-4). Defaults vary by provider.')
parser.add_argument('--api-key', type=str, help='API Key (optional if env var is set)') parser.add_argument('--api-key', type=str, help='API Key (optional if env var is set)')
parser.add_argument('--api-base', type=str, help='Custom API Base URL (useful for custom Ollama ports)') parser.add_argument('--api-base', type=str, help='Custom API Base URL (useful for custom Ollama ports)')

View File

@ -324,7 +324,7 @@
<li class="nav-item mx-2 mb-1"> {% comment %} <li class="nav-item mx-2 mb-1">
{% if request.user.user_type == 'candidate' and request.user.is_authenticated and request.user.profile_image.url %} {% if request.user.user_type == 'candidate' and request.user.is_authenticated and request.user.profile_image.url %}
<a href="{% url 'applicant_portal_dashboard' %}" class="mx-2"> <a href="{% url 'applicant_portal_dashboard' %}" class="mx-2">
<img src="{{ request.user.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar" <img src="{{ request.user.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar"
@ -334,43 +334,104 @@
{% else %} {% else %}
<a class="nav-link text-primary-theme" href="{% url 'applicant_portal_dashboard' %}">{% trans "Profile" %}</a> <a class="nav-link text-primary-theme" href="{% url 'applicant_portal_dashboard' %}">{% trans "Profile" %}</a>
{% endif %} {% endif %}
</li> </li> {% endcomment %}
<li class="nav-item mx-2 mb-1"> {% if request.resolver_match.url_name != "kaauh_career" %}
<a class="nav-link text-secondary text-primary-theme" href="{% url 'kaauh_career' %}">{% trans "Careers" %}</a> <li class="nav-item mx-2 mb-1">
</li> <a class="nav-link text-secondary text-primary-theme" href="{% url 'kaauh_career' %}">{% trans "Careers" %}</a>
</li>
{% endif %}
<li class="nav-item me-2 d-none d-lg-block">
<li class="nav-item dropdown"> {% if LANGUAGE_CODE == 'en' %}
<form action="{% url 'set_language' %}" method="post" class="d-inline">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button name="language" value="ar" class="btn bg-primary-theme text-primary-theme" type="submit">
<span class="me-2">🇸🇦</span> العربية
</button>
</form>
{% elif LANGUAGE_CODE == 'ar' %}
<form action="{% url 'set_language' %}" method="post" class="d-inline">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button name="language" value="en" class="btn bg-primary-theme text-primary-theme" type="submit">
<span class="me-2">🇺🇸</span> English
</button>
</form>
{% endif %}
</li>
<li class="nav-item dropdown">
<button class="language-toggle-btn dropdown-toggle" type="button" <button class="language-toggle-btn dropdown-toggle" type="button"
data-bs-toggle="dropdown" data-bs-offset="0, 8" aria-expanded="false" data-bs-toggle="dropdown" data-bs-offset="0, 8" aria-expanded="false"
aria-label="{% trans 'Toggle language menu' %}"> aria-label="{% trans 'Toggle language menu' %}">
<i class="fas fa-globe"></i>
<span class="d-inline">{{ LANGUAGE_CODE|upper }}</span> <span class="d-inline"></span>
{% if user.profile_image %}
<img src="{{ user.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' %}">
{% if user.first_name %}
{{ user.first_name|first|capfirst }} {{ user.last_name|first|capfirst }}
{% else %}
{{user.username|first|capfirst}}
{% endif %}
</div>
{% endif %}
</button> </button>
<ul class="dropdown-menu mx-auto {% if LANGUAGE_CODE == 'ar' %}dropdown-menu-end{% else %}dropdown-menu-end{% endif %}" aria-labelledby="navbarLanguageDropdown"> <ul class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3" style="min-width: 240px;">
<li class="px-4 py-3">
<div class="d-flex align-items-center">
<div class="me-3 d-flex align-items-center justify-content-center" style="min-width: 48px;">
{% if user.profile_image %}
<img src="{{ user.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 text-primary-theme"
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>
<div class="text-muted small">{{ user.email|truncatechars:24 }}</div>
</div>
</div>
</li>
<li> <li><hr class="dropdown-divider my-1"></li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button name="language" value="en" class="dropdown-item {% if LANGUAGE_CODE == 'en' %}active bg-light-subtle{% endif %}" type="submit">
<span class="flag-emoji">🇺🇸</span>
<span class="language-text">English</span>
</button>
</form>
</li>
<li> <li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %} <a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-primary-theme" href="{% url 'applicant_portal_dashboard' %}">
<input name="next" type="hidden" value="{{ request.get_full_path }}"> <i class="fas fa-tachometer-alt me-3 fs-5"></i> <span>{% trans "Dashboard" %}</span>
<button name="language" value="ar" class="dropdown-item {% if LANGUAGE_CODE == 'ar' %}active bg-light-subtle{% endif %}" type="submit"> </a>
<span class="flag-emoji">🇸🇦</span> </li>
<span class="language-text">العربية (Arabic)</span> <li>
</button> <a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-primary-theme" href="{% url 'user_detail' request.user.pk %}">
</form> <i class="fas fa-user-circle me-3 fs-5"></i> <span>{% trans "My Profile" %}</span>
</li> </a>
</ul> </li>
</li>
<li><hr class="dropdown-divider my-1"></li>
<li>
<form method="post" action="{% url 'account_logout'%}" class="d-inline">
{% csrf_token %}
<button
type="submit"
class="dropdown-item py-2 px-4 d-flex align-items-center border-0 bg-transparent text-start w-100"
aria-label="{% trans 'Sign out' %}"
>
<i class="fas fa-sign-out-alt me-3 fs-5" style="color:red;"></i>
<span style="color:red;">{% trans "Sign Out" %}</span>
</button>
</form>
</li>
</ul>
</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -374,8 +374,8 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="d-flex justify-content-between align-items-center flex-wrap max-width-1600"> <div class="d-flex justify-content-between align-items-center flex-wrap max-width-1600">
<p class="mb-0 text-white-50"> <p class="mb-0 text-white-50">
&copy; {% now "Y" %} {% trans "King Abdullah Academic University Hospital (KAAUH)." %} {% comment %} &copy; {% now "Y" %} {% trans "King Abdullah Academic University Hospital (KAAUH)." %}
{% trans "All rights reserved." %} {% trans "All rights reserved." %} {% endcomment %}
</p> </p>
<a class="text-decoration-none" href="https://tenhal.sa/" target='_blank'> <a class="text-decoration-none" href="https://tenhal.sa/" target='_blank'>
<p class="mb-0 text-white-50"> <p class="mb-0 text-white-50">

View File

@ -223,7 +223,7 @@
{% block customJS %} {% block customJS %}
<script> <script>
// Auto-refresh unread count every 30 seconds // Auto-refresh unread count every 30 seconds
setInterval(() => { /*setInterval(() => {
fetch('/api/unread-count/') fetch('/api/unread-count/')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -236,5 +236,6 @@ setInterval(() => {
}) })
.catch(error => console.error('Error fetching unread count:', error)); .catch(error => console.error('Error fetching unread count:', error));
}, 30000); }, 30000);
*/
</script> </script>
{% endblock %} {% endblock %}

View File

@ -45,11 +45,15 @@
<div class="row g-4"> <div class="row g-4">
<div class="col-md-6"> <div class="col-md-6">
<small class="text-muted d-block mb-1">{% trans "From:" %}</small> <small class="text-muted d-block mb-1">{% trans "From:" %}</small>
<span class="fw-semibold">{{ message.sender.get_full_name|default:message.sender.username }}</span> <span class="fw-semibold">{{ message.sender.get_full_name|default:message.sender.username }} <br>
<span class="text-muted" style="font-size: 0.8em">{{ message.sender.email }}</span>
</span>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<small class="text-muted d-block mb-1">{% trans "To:" %}</small> <small class="text-muted d-block mb-1">{% trans "To:" %}</small>
<span class="fw-semibold">{{ message.recipient.get_full_name|default:message.recipient.username }}</span> <span class="fw-semibold">{{ message.recipient.get_full_name|default:message.recipient.username }} <br>
<span class="text-muted" style="font-size: 0.8em">{{ message.recipient.email }}</span>
</span>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<small class="text-muted d-block mb-1">{% trans "Type:" %}</small> <small class="text-muted d-block mb-1">{% trans "Type:" %}</small>
@ -75,9 +79,13 @@
{% if message.job %} {% if message.job %}
<div class="col-md-6"> <div class="col-md-6">
<small class="text-muted d-block mb-1">{% trans "Related Job:" %}</small> <small class="text-muted d-block mb-1">{% trans "Related Job:" %}</small>
<a href="{% url 'job_detail' message.job.slug %}" class="fw-semibold text-decoration-none text-primary-theme"> {% if request.user.user_type == "staff" %}
<a href="{% url 'job_detail' message.job.slug %}" class="fw-semibold text-decoration-none text-primary-theme">
{{ message.job.title }}
</a>
{% else %}
{{ message.job.title }} {{ message.job.title }}
</a> {% endif %}
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@ -116,7 +116,8 @@
{% if message.sender == request.user %} {% if message.sender == request.user %}
{% trans "Me"%} {% trans "Me"%}
{% else %} {% else %}
{{ message.sender.get_full_name|default:message.sender.username }} {{ message.sender.get_full_name|default:message.sender.username }} <br>
<span class="text-muted" style="font-size: 0.8em">{{ message.sender.email }}</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
@ -124,6 +125,8 @@
{% trans "Me"%} {% trans "Me"%}
{% else %} {% else %}
{{ message.recipient.get_full_name|default:message.recipient.username }} {{ message.recipient.get_full_name|default:message.recipient.username }}
<br>
<span class="text-muted" style="font-size: 0.8em">{{ message.recipient.email }}</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
@ -232,7 +235,7 @@
{% block customJS %} {% block customJS %}
<script> <script>
// Auto-refresh unread count every 30 seconds // Auto-refresh unread count every 30 seconds
setInterval(() => { /*setInterval(() => {
fetch('/api/unread-count/') fetch('/api/unread-count/')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -245,5 +248,6 @@ setInterval(() => {
}) })
.catch(error => console.error('Error fetching unread count:', error)); .catch(error => console.error('Error fetching unread count:', error));
}, 30000); }, 30000);
*/
</script> </script>
{% endblock %} {% endblock %}

View File

@ -252,8 +252,8 @@
{% trans "GPA" %} {% trans "GPA" %}
</label> </label>
<input type="number" name="GPA" id="gpa" class="form-control form-control-sm" <input type="number" name="GPA" id="gpa" class="form-control form-control-sm"
value="{{ gpa }}" min="0" max="4" value="{{ gpa }}" step="0.01" min="0" max="4"
placeholder="e.g., 4" style="width: 120px;"> placeholder="e.g., 3.5" style="width: 120px;">
</div> </div>
<div class="col-auto"> <div class="col-auto">
<label for="min_ai_score" class="form-label small text-muted mb-1"> <label for="min_ai_score" class="form-label small text-muted mb-1">

View File

@ -192,7 +192,7 @@
{% block customJS %} {% block customJS %}
<script> <script>
document.addEventListener('DOMContentLoaded', function() { /*document.addEventListener('DOMContentLoaded', function() {
// Auto-refresh notification count every 30 seconds // Auto-refresh notification count every 30 seconds
setInterval(function() { setInterval(function() {
fetch('/api/notification-count/') fetch('/api/notification-count/')
@ -212,5 +212,6 @@ document.addEventListener('DOMContentLoaded', function() {
.catch(error => console.error('Error fetching notifications:', error)); .catch(error => console.error('Error fetching notifications:', error));
}, 30000); }, 30000);
}); });
*/
</script> </script>
{% endblock %} {% endblock %}

View File

@ -207,6 +207,7 @@
{% block customJS %} {% block customJS %}
<script> <script>
/*
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Auto-refresh notifications every 30 seconds // Auto-refresh notifications every 30 seconds
setInterval(function() { setInterval(function() {
@ -227,5 +228,6 @@ document.addEventListener('DOMContentLoaded', function() {
.catch(error => console.error('Error fetching notifications:', error)); .catch(error => console.error('Error fetching notifications:', error));
}, 30000); }, 30000);
}); });
*/
</script> </script>
{% endblock %} {% endblock %}

View File

@ -64,14 +64,6 @@
<!-- Settings Table --> <!-- Settings Table -->
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% if page_obj %} {% if page_obj %}
<div class="table-responsive"> <div class="table-responsive">

View File

@ -266,7 +266,7 @@
<label for="{{ form.source_type.id_for_label }}" class="form-label"> <label for="{{ form.source_type.id_for_label }}" class="form-label">
{{ form.source_type.label }} <span class="text-danger">*</span> {{ form.source_type.label }} <span class="text-danger">*</span>
</label> </label>
{{ form.source_type|add_class:"form-select" }} {{ form.source_type|add_class:"form-control" }}
{% if form.source_type.errors %} {% if form.source_type.errors %}
<div class="invalid-feedback d-block"> <div class="invalid-feedback d-block">
{% for error in form.source_type.errors %} {% for error in form.source_type.errors %}

View File

@ -9,9 +9,9 @@
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li>
<li class="breadcrumb-item active" aria-current="page" style=" <li class="breadcrumb-item active" aria-current="page" style="
color: #F43B5E; /* Rosy Accent Color */ color: #F43B5E; /* Rosy Accent Color */
font-weight: 600; font-weight: 600;
">{% trans "Sources Settings" %}</li> ">{% trans "Sources Settings" %}</li>
</ol> </ol>
</nav> </nav>
<div class="row"> <div class="row">