diff --git a/.gitignore b/.gitignore index 7000088..d098f46 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ var/ *.log *.pot *.sqlite3 -local_settings.py +settings.py db.sqlite3 # Virtual environment @@ -95,7 +95,7 @@ coverage.xml # Django stuff: # Local settings -local_settings.py +settings.py # Database sqlite files: # The base directory for relative paths in .gitignore diff --git a/NorahUniversity/__pycache__/settings.cpython-312.pyc b/NorahUniversity/__pycache__/settings.cpython-312.pyc index 48c95df..5d650c3 100644 Binary files a/NorahUniversity/__pycache__/settings.cpython-312.pyc and b/NorahUniversity/__pycache__/settings.cpython-312.pyc differ diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index 4e7bb3d..cecb890 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -66,7 +66,7 @@ INSTALLED_APPS = [ SITE_ID = 1 -LOGIN_REDIRECT_URL = '/' +LOGIN_REDIRECT_URL = 'dashboard' ACCOUNT_LOGOUT_REDIRECT_URL = '/' @@ -135,9 +135,9 @@ WSGI_APPLICATION = 'NorahUniversity.wsgi.application' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'norahuniversity', - 'USER': 'norahuniversity', - 'PASSWORD': 'norahuniversity', + 'NAME': 'haikal_db', + 'USER': 'faheed', + 'PASSWORD': 'Faheed@215', 'HOST': '127.0.0.1', 'PORT': '5432', } @@ -183,32 +183,19 @@ ACCOUNT_LOGIN_METHODS = ['email'] ACCOUNT_SIGNUP_FIELDS = ['email*', 'password1*', 'password2*'] ACCOUNT_UNIQUE_EMAIL = True -ACCOUNT_EMAIL_VERIFICATION = 'none' ACCOUNT_USER_MODEL_USERNAME_FIELD = None -ACCOUNT_EMAIL_VERIFICATION = "mandatory" +ACCOUNT_EMAIL_VERIFICATION = "mandatory" ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True ACCOUNT_FORMS = {'signup': 'recruitment.forms.StaffSignupForm'} -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_HOST = '10.10.1.110' #'smtp.gmail.com' -EMAIL_PORT = 2225 #587 -EMAIL_USE_TLS = False -EMAIL_USE_SSL = False -EMAIL_TIMEOUT = 10 - -DEFAULT_FROM_EMAIL = 'norahuniversity@example.com' - -# Gmail SMTP credentials -# Remove the comment below if you want to use Gmail SMTP server -# EMAIL_HOST_USER = 'your_email@gmail.com' -# EMAIL_HOST_PASSWORD = 'your_password' +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # Crispy Forms Configuration CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" -CRISPY_TEMPLATE_PACK = "bootstrap5" +CRISPY_TEMPLATE_PACK = "bootstrapconsole5" # Bootstrap 5 Configuration CRISPY_BS5 = { diff --git a/recruitment/__pycache__/forms.cpython-312.pyc b/recruitment/__pycache__/forms.cpython-312.pyc index a61fc29..9c3d6af 100644 Binary files a/recruitment/__pycache__/forms.cpython-312.pyc and b/recruitment/__pycache__/forms.cpython-312.pyc differ diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc index 7e98f6b..a2a7d03 100644 Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc index e468e4d..9e1e55a 100644 Binary files a/recruitment/__pycache__/urls.cpython-312.pyc and b/recruitment/__pycache__/urls.cpython-312.pyc differ diff --git a/recruitment/__pycache__/utils.cpython-312.pyc b/recruitment/__pycache__/utils.cpython-312.pyc index 3c1ee40..dc3c1d7 100644 Binary files a/recruitment/__pycache__/utils.cpython-312.pyc and b/recruitment/__pycache__/utils.cpython-312.pyc differ diff --git a/recruitment/__pycache__/views.cpython-312.pyc b/recruitment/__pycache__/views.cpython-312.pyc index 9e9649a..382a143 100644 Binary files a/recruitment/__pycache__/views.cpython-312.pyc and b/recruitment/__pycache__/views.cpython-312.pyc differ diff --git a/recruitment/__pycache__/views_frontend.cpython-312.pyc b/recruitment/__pycache__/views_frontend.cpython-312.pyc index a19c31f..04d5aa8 100644 Binary files a/recruitment/__pycache__/views_frontend.cpython-312.pyc and b/recruitment/__pycache__/views_frontend.cpython-312.pyc differ diff --git a/recruitment/email_service.py b/recruitment/email_service.py index 733d2a3..a048cf9 100644 --- a/recruitment/email_service.py +++ b/recruitment/email_service.py @@ -162,10 +162,17 @@ def send_interview_invitation_email(candidate, job, meeting_details=None, recipi try: # Prepare recipient list recipients = [] - if candidate.email: + if candidate.hiring_source == "Agency": + try: + recipients.append(candidate.hiring_agency.email) + except : + pass + else: recipients.append(candidate.email) + if recipient_list: recipients.extend(recipient_list) + if not recipients: return {'success': False, 'error': 'No recipient email addresses provided'} diff --git a/recruitment/models.py b/recruitment/models.py index 802b380..ad673b9 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -682,7 +682,30 @@ class Candidate(Base): @property def scoring_timeout(self): return timezone.now() <= (self.created_at + timezone.timedelta(minutes=5)) - + + @property + def get_interview_date(self): + if hasattr(self, 'scheduled_interview') and self.scheduled_interview: + return self.scheduled_interviews.first().interview_date + return None + + + + + + @property + def get_interview_time(self): + if hasattr(self, 'scheduled_interview') and self.scheduled_interview: + return self.scheduled_interviews.first().interview_time + return None + + + @property + def time_to_hire_days(self): + if self.hired_date and self.created_at: + time_to_hire = self.hired_date - self.created_at.date() + return time_to_hire.days + return 0 class TrainingMaterial(Base): title = models.CharField(max_length=255, verbose_name=_("Title")) diff --git a/recruitment/urls.py b/recruitment/urls.py index 97f6e7d..cb16bf2 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -5,7 +5,7 @@ from . import views_integration from . import views_source urlpatterns = [ - path('', views_frontend.dashboard_view, name='dashboard'), + path('dashboard/', views_frontend.dashboard_view, name='dashboard'), # Job URLs (using JobPosting model) path('jobs/', views_frontend.JobListView.as_view(), name='job_list'), @@ -62,6 +62,7 @@ urlpatterns = [ # Form Preview URLs # path('forms/', views.form_list, name='form_list'), + path('forms/builder/', views.form_builder, name='form_builder'), path('forms/builder//', views.form_builder, name='form_builder'), path('forms/', views.form_templates_list, name='form_templates_list'), diff --git a/recruitment/utils.py b/recruitment/utils.py index af759df..afed297 100644 --- a/recruitment/utils.py +++ b/recruitment/utils.py @@ -449,7 +449,7 @@ def schedule_interviews(schedule): interview_date=slot['date'], interview_time=slot['time'] ) - + candidate.interview_date=interview_datetime # Send email to candidate send_interview_email(scheduled_interview) diff --git a/recruitment/views.py b/recruitment/views.py index 5a7a3fb..b56ea09 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -860,13 +860,13 @@ def application_submit_form(request, template_slug): if is_limit_exceeded: messages.error( request, - 'Application limit reached: This job is no longer accepting new applications. Please explore other available positions.' + _('Application limit reached: This job is no longer accepting new applications.') ) return redirect('application_detail',slug=job.slug) if job.is_expired: messages.error( request, - 'Application deadline passed: This job is no longer accepting new applications. Please explore other available positions.' + _('Application deadline passed: This job is no longer accepting new applications.') ) return redirect('application_detail',slug=job.slug) @@ -1424,10 +1424,26 @@ def candidate_set_exam_date(request, slug): def candidate_update_status(request, slug): job = get_object_or_404(JobPosting, slug=slug) mark_as = request.POST.get('mark_as') + if mark_as != '----------': candidate_ids = request.POST.getlist("candidate_ids") + print(candidate_ids) if c := Candidate.objects.filter(pk__in = candidate_ids): - c.update(stage=mark_as,exam_date=timezone.now(),applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") + + if mark_as=='Exam': + c.update(exam_date=timezone.now(),interview_date=None,offer_date=None,hired_date=None,stage=mark_as,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") + elif mark_as=='Interview': + # interview_date update when scheduling the interview + c.update(stage=mark_as,offer_date=None,hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") + elif mark_as=='Offer': + c.update(stage=mark_as,offer_date=timezone.now(),hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") + elif mark_as=='Hired': + print('hired') + c.update(stage=mark_as,hired_date=timezone.now(),applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") + else: + c.update(stage=mark_as,exam_date=None,interview_date=None,offer_date=None,hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") + + messages.success(request, f"Candidates Updated") response = HttpResponse(redirect("candidate_screening_view", slug=job.slug)) @@ -2973,11 +2989,12 @@ def agency_assignment_create(request,slug=None): messages.success(request, f'Assignment created for {assignment.agency.name} - {assignment.job.title}!') return redirect('agency_assignment_detail', slug=assignment.slug) else: - messages.error(request, 'Please correct the errors below.') + messages.error(request, f'Please correct the errors below.{form.errors.as_text()}') + print(form.errors.as_json()) else: form = AgencyJobAssignmentForm() try: - from django.forms import HiddenInput + # from django.forms import HiddenInput form.initial['agency'] = agency # form.fields['agency'].widget = HiddenInput() except HiringAgency.DoesNotExist: @@ -3086,6 +3103,7 @@ def agency_access_link_detail(request, slug): AgencyAccessLink.objects.select_related('assignment__agency', 'assignment__job'), slug=slug ) + context = { 'access_link': access_link, diff --git a/recruitment/views_frontend.py b/recruitment/views_frontend.py index 729749e..0f4b9c0 100644 --- a/recruitment/views_frontend.py +++ b/recruitment/views_frontend.py @@ -257,6 +257,7 @@ def candidate_detail(request, slug): if request.user.is_staff: stage_form = forms.CandidateStageForm() + # parsed = JSON(json.dumps(parsed), indent=2, highlight=True, skip_keys=False, ensure_ascii=False, check_circular=True, allow_nan=True, default=None, sort_keys=False) # parsed = json_to_markdown_table([parsed]) return render(request, 'recruitment/candidate_detail.html', { @@ -458,19 +459,25 @@ def dashboard_view(request): # B. Efficiency & Conversion Metrics (Scoped) hired_candidates = candidate_queryset.filter( - Q(offer_status="Accepted") | Q(stage='HIRED'), - join_date__isnull=False + stage='Hired' ) + print(hired_candidates) + lst=[c.time_to_hire_days for c in hired_candidates] + print(lst) time_to_hire_query = hired_candidates.annotate( time_diff=ExpressionWrapper( - F('join_date') - F('created_at__date'), + F('hired_date') - F('created_at__date'), output_field=fields.DurationField() ) ).aggregate(avg_time_to_hire=Avg('time_diff')) + + print(time_to_hire_query) + avg_time_to_hire_days = ( time_to_hire_query.get('avg_time_to_hire').days if time_to_hire_query.get('avg_time_to_hire') else 0 ) + print(avg_time_to_hire_days) applied_count = candidate_queryset.filter(stage='Applied').count() advanced_count = candidate_queryset.filter(stage__in=['Exam', 'Interview', 'Offer']).count() diff --git a/templates/jobs/application_success.html b/templates/jobs/application_success.html index c7f2634..2a0699f 100644 --- a/templates/jobs/application_success.html +++ b/templates/jobs/application_success.html @@ -4,7 +4,7 @@ - {% translate "Application Submitted - Thank You" %} + {% trans "Application Submitted - Thank You" %}