From e9c76dfe1845eb8e89a440f96adb131e0c0b1511 Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 12 Dec 2025 22:49:20 +0300 Subject: [PATCH] update and more fixes --- .env | 6 +- NorahUniversity/settings.py | 3 +- recruitment/forms.py | 63 +++++++-- recruitment/migrations/0001_initial.py | 2 +- ...er_source_name_alter_source_source_type.py | 23 ++++ recruitment/models.py | 4 +- recruitment/tasks.py | 3 +- recruitment/utils.py | 1 + recruitment/views.py | 76 +++++++---- run.py | 4 +- .../partials/candidate_facing_base.html | 121 +++++++++++++----- templates/base.html | 4 +- .../messages/application_message_list.html | 3 +- templates/messages/message_detail.html | 16 ++- templates/messages/message_list.html | 8 +- .../applications_screening_view.html | 4 +- .../recruitment/notification_detail.html | 3 +- templates/recruitment/notification_list.html | 2 + templates/recruitment/settings_list.html | 8 -- templates/recruitment/source_form.html | 2 +- templates/recruitment/source_list.html | 6 +- 21 files changed, 261 insertions(+), 101 deletions(-) create mode 100644 recruitment/migrations/0002_alter_source_name_alter_source_source_type.py diff --git a/.env b/.env index 8d7fbd5..b9e2bf0 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -DB_NAME=haikal_db -DB_USER=faheed -DB_PASSWORD=Faheed@215 \ No newline at end of file +DB_NAME=norahuniversity +DB_USER=norahuniversity +DB_PASSWORD=norahuniversity \ No newline at end of file diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index 35a1699..bc0e691 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -208,7 +208,8 @@ ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True 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_PORT = 2225 diff --git a/recruitment/forms.py b/recruitment/forms.py index d50bd1d..60e043a 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -58,14 +58,12 @@ class SourceForm(forms.ModelForm): "name": forms.TextInput( attrs={ "class": "form-control", - "placeholder": "e.g., ATS System, ERP Integration", "required": True, } ), "source_type": forms.TextInput( attrs={ "class": "form-control", - "placeholder": "e.g., ATS, ERP, API", "required": True, } ), @@ -73,16 +71,15 @@ class SourceForm(forms.ModelForm): attrs={ "class": "form-control", "rows": 3, - "placeholder": "Brief description of the source system", } ), "ip_address": forms.TextInput( - attrs={"class": "form-control", "placeholder": "192.168.1.100", + attrs={"class": "form-control", "required":True}, ), "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"}), } @@ -318,7 +315,7 @@ class PersonForm(forms.ModelForm): pass return email.strip() - + class ApplicationForm(forms.ModelForm): @@ -1930,6 +1927,52 @@ class ScheduledInterviewForm(forms.Form): raise forms.ValidationError(_('Start time cannot be in the past.')) 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): status = forms.ChoiceField( choices=ScheduledInterview.InterviewStatus.choices, @@ -2034,7 +2077,7 @@ class SettingsForm(forms.ModelForm): class InterviewEmailForm(forms.Form): """Form for composing emails to participants about a candidate""" to = forms.CharField( - + label=_('To'), # Use a descriptive label required=True, @@ -2069,12 +2112,12 @@ class InterviewEmailForm(forms.Form): if application.hiring_agency: self.fields['to'].initial=application.hiring_agency.email self.fields['to'].disabled= True - - + + else: self.fields['to'].initial=application.person.email self.fields['to'].disabled= True - + # Set initial message with candidate and meeting info diff --git a/recruitment/migrations/0001_initial.py b/recruitment/migrations/0001_initial.py index 96c61a1..2aaf4db 100644 --- a/recruitment/migrations/0001_initial.py +++ b/recruitment/migrations/0001_initial.py @@ -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.validators diff --git a/recruitment/migrations/0002_alter_source_name_alter_source_source_type.py b/recruitment/migrations/0002_alter_source_name_alter_source_source_type.py new file mode 100644 index 0000000..3b4ce6a --- /dev/null +++ b/recruitment/migrations/0002_alter_source_name_alter_source_source_type.py @@ -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'), + ), + ] diff --git a/recruitment/models.py b/recruitment/models.py index 4d586ac..dced2ab 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -1748,10 +1748,10 @@ class Source(Base): max_length=100, unique=True, verbose_name=_("Source Name"), - help_text=_("e.g., ATS, ERP "), + help_text=_("Name of the source"), ) 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( blank=True, diff --git a/recruitment/tasks.py b/recruitment/tasks.py index 9cf5fc8..3ee69b0 100644 --- a/recruitment/tasks.py +++ b/recruitment/tasks.py @@ -680,7 +680,7 @@ def create_interview_and_meeting(schedule_id): try: schedule = ScheduledInterview.objects.get(pk=schedule_id) interview = schedule.interview - + print("creating zoooooooooooooooooooooooooooooooooooooom meeting") result = create_zoom_meeting(interview.topic, interview.start_time, interview.duration) 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'. # We rely on the unique 'id' that maps to your ZoomMeeting.meeting_id field. meeting_id_zoom = str(object_data.get('id')) - print(meeting_id_zoom) if not meeting_id_zoom: logger.warning(f"Webhook received without a valid Meeting ID: {event_type}") return False diff --git a/recruitment/utils.py b/recruitment/utils.py index 7da5dd4..13fea30 100644 --- a/recruitment/utils.py +++ b/recruitment/utils.py @@ -426,6 +426,7 @@ def create_zoom_meeting(topic, start_time, duration): "Content-Type": "application/json" } ZOOM_MEETING_URL = get_setting('ZOOM_MEETING_URL') + print(ZOOM_MEETING_URL) response = requests.post( ZOOM_MEETING_URL, headers=headers, diff --git a/recruitment/views.py b/recruitment/views.py index 27972e5..342689b 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -187,7 +187,7 @@ class PersonListView(StaffRequiredMixin, ListView, LoginRequiredMixin): def get_queryset(self): queryset = super().get_queryset().select_related("user") search_query = self.request.GET.get("search", "") - print(Person.objects.first().last_name) + if search_query: queryset=queryset.filter( Q(first_name=search_query) | @@ -226,10 +226,10 @@ class PersonCreateView(CreateView, LoginRequiredMixin, StaffOrAgencyRequiredMixi template_name = "people/create_person.html" form_class = PersonForm success_url = reverse_lazy("person_list") - + def form_valid(self, form): - + instance = form.save() view = self.request.POST.get("view") if view == "portal": @@ -239,8 +239,8 @@ class PersonCreateView(CreateView, LoginRequiredMixin, StaffOrAgencyRequiredMixi print(agency) instance.agency = agency instance.save() - - + + # 2. Add the content to update (e.g., re-render the person list table) # response.content = render_to_string('recruitment/persons_table.html', return redirect("agency_portal_persons_list") @@ -1910,8 +1910,7 @@ def applications_screening_view(request, slug): ai_analysis_data__analysis_data_en__screening_stage_rating=screening_rating ) if gpa: - applications = applications.filter(person__gpa__gt=gpa) - print(applications) + applications = applications.filter(person__gpa__gte=gpa) if tier1_count > 0: applications = applications[:tier1_count] @@ -2131,27 +2130,43 @@ def applications_document_review_view(request, slug): @staff_user_required def reschedule_meeting_for_application(request, slug): from .utils import update_meeting + from .forms import OnsiteScheduleInterviewUpdateForm schedule = get_object_or_404(ScheduledInterview, slug=slug) + interview = schedule.interview + 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(): topic = form.cleaned_data.get("topic") start_time = form.cleaned_data.get("start_time") duration = form.cleaned_data.get("duration") - updated_data = { - "topic": topic, - "start_time": start_time.isoformat() + "Z", - "duration": duration, - } - result = update_meeting(schedule.interview, updated_data) + physical_address = form.cleaned_data.get("physical_address") + room_number = form.cleaned_data.get("room_number") + if interview.location_type == "Remote": + updated_data = { + "topic": topic, + "start_time": start_time.isoformat() + "Z", + "duration": duration, + } + result = update_meeting(schedule.interview, updated_data) - if result["status"] == "success": - messages.success(request, result["message"]) + if result["status"] == "success": + messages.success(request, result["message"]) + else: + messages.error(request, result["message"]) 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: - print(form.errors) messages.error(request, "Invalid data submitted.") return redirect("interview_detail", slug=schedule.slug) @@ -3119,7 +3134,7 @@ def agency_portal_persons_list(request): | Q(last_name__icontains=search_query) | Q(email__icontains=search_query) | Q(phone=search_query) - + ) paginator = Paginator(persons, 20) # Show 20 persons per page @@ -4395,7 +4410,7 @@ def compose_application_email(request, slug): request=request, attachments=None, async_task_=True, # Changed to False to avoid pickle issues - from_interview=False, + # from_interview=False, job=job, ) @@ -4791,14 +4806,23 @@ def interview_list(request): @staff_user_required def interview_detail(request, slug): """View details of a specific interview""" - from .forms import ScheduledInterviewUpdateStatusForm + from .forms import ScheduledInterviewUpdateStatusForm,OnsiteScheduleInterviewUpdateForm schedule = get_object_or_404(ScheduledInterview, slug=slug) interview = schedule.interview application=schedule.application 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['start_time'] = interview.start_time + reschedule_form.initial['duration'] = interview.duration + meeting=interview interview_email_form=InterviewEmailForm(job,application,schedule) context = { @@ -5396,14 +5420,14 @@ class ApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView): super() .get_queryset() .select_related("person", "job") - + ) # Handle search search_query = self.request.GET.get("search", "") job = self.request.GET.get("job", "") stage = self.request.GET.get("stage", "") - + if search_query: queryset = queryset.filter( Q(person__first_name=search_query) | @@ -6109,9 +6133,9 @@ STAGE_CONFIG = { @login_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""" - job = get_object_or_404(JobPosting, slug=job_slug) + job = get_object_or_404(JobPosting, slug=slug) # Validate stage if stage not in STAGE_CONFIG: diff --git a/run.py b/run.py index 457c9ce..e5f07b7 100644 --- a/run.py +++ b/run.py @@ -227,10 +227,10 @@ if __name__ == "__main__": 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('--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 - 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('--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)') diff --git a/templates/applicant/partials/candidate_facing_base.html b/templates/applicant/partials/candidate_facing_base.html index 67efe51..0dd7d6f 100644 --- a/templates/applicant/partials/candidate_facing_base.html +++ b/templates/applicant/partials/candidate_facing_base.html @@ -324,7 +324,7 @@ - - + {% endcomment %} + {% if request.resolver_match.url_name != "kaauh_career" %} + + {% endif %} - - + diff --git a/templates/base.html b/templates/base.html index 287daa6..4eb2044 100644 --- a/templates/base.html +++ b/templates/base.html @@ -374,8 +374,8 @@

- © {% now "Y" %} {% trans "King Abdullah Academic University Hospital (KAAUH)." %} - {% trans "All rights reserved." %} + {% comment %} © {% now "Y" %} {% trans "King Abdullah Academic University Hospital (KAAUH)." %} + {% trans "All rights reserved." %} {% endcomment %}

diff --git a/templates/messages/application_message_list.html b/templates/messages/application_message_list.html index 2f9d34c..5ddb5e0 100644 --- a/templates/messages/application_message_list.html +++ b/templates/messages/application_message_list.html @@ -223,7 +223,7 @@ {% block customJS %} {% endblock %} \ No newline at end of file diff --git a/templates/messages/message_detail.html b/templates/messages/message_detail.html index 23f31d5..99a74d4 100644 --- a/templates/messages/message_detail.html +++ b/templates/messages/message_detail.html @@ -45,11 +45,15 @@

{% trans "From:" %} - {{ message.sender.get_full_name|default:message.sender.username }} + {{ message.sender.get_full_name|default:message.sender.username }}
+ {{ message.sender.email }} +
{% trans "To:" %} - {{ message.recipient.get_full_name|default:message.recipient.username }} + {{ message.recipient.get_full_name|default:message.recipient.username }}
+ {{ message.recipient.email }} +
diff --git a/templates/messages/message_list.html b/templates/messages/message_list.html index e05793a..7692dea 100644 --- a/templates/messages/message_list.html +++ b/templates/messages/message_list.html @@ -116,7 +116,8 @@ {% if message.sender == request.user %} {% trans "Me"%} {% else %} - {{ message.sender.get_full_name|default:message.sender.username }} + {{ message.sender.get_full_name|default:message.sender.username }}
+ {{ message.sender.email }} {% endif %} @@ -124,6 +125,8 @@ {% trans "Me"%} {% else %} {{ message.recipient.get_full_name|default:message.recipient.username }} +
+ {{ message.recipient.email }} {% endif %} @@ -232,7 +235,7 @@ {% block customJS %} {% endblock %} \ No newline at end of file diff --git a/templates/recruitment/applications_screening_view.html b/templates/recruitment/applications_screening_view.html index 42b401e..1c2e8b4 100644 --- a/templates/recruitment/applications_screening_view.html +++ b/templates/recruitment/applications_screening_view.html @@ -252,8 +252,8 @@ {% trans "GPA" %} + value="{{ gpa }}" step="0.01" min="0" max="4" + placeholder="e.g., 3.5" style="width: 120px;">