From 6b74990791e7d312be28423b42a9f618045f3aae Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 14 Oct 2025 19:28:12 +0300 Subject: [PATCH] update the interview stage --- .../__pycache__/settings.cpython-313.pyc | Bin 7678 -> 7693 bytes NorahUniversity/settings.py | 7 +- recruitment/__pycache__/forms.cpython-313.pyc | Bin 23612 -> 23612 bytes .../__pycache__/models.cpython-313.pyc | Bin 45158 -> 45802 bytes recruitment/__pycache__/urls.cpython-313.pyc | Bin 8956 -> 9373 bytes recruitment/__pycache__/utils.cpython-313.pyc | Bin 18670 -> 20463 bytes recruitment/__pycache__/views.cpython-313.pyc | Bin 70640 -> 71654 bytes recruitment/models.py | 24 +- recruitment/urls.py | 5 +- recruitment/utils.py | 35 + recruitment/views.py | 165 ++--- templates/base.html | 306 +-------- templates/interviews/schedule_interviews.html | 337 +++++++--- templates/meetings/delete_meeting_form.html | 7 + templates/meetings/reschedule_meeting.html | 70 ++ templates/meetings/schedule_meeting_form.html | 90 +++ templates/meetings/update_meeting.html | 166 +---- .../recruitment/candidate_exam_view.html | 283 +-------- .../recruitment/candidate_interview_view.html | 597 ++++++++---------- .../recruitment/candidate_screening_view.html | 268 ++------ 20 files changed, 902 insertions(+), 1458 deletions(-) create mode 100644 templates/meetings/delete_meeting_form.html create mode 100644 templates/meetings/reschedule_meeting.html create mode 100644 templates/meetings/schedule_meeting_form.html diff --git a/NorahUniversity/__pycache__/settings.cpython-313.pyc b/NorahUniversity/__pycache__/settings.cpython-313.pyc index 23e0028488eca88c9f350625a6b242e64d327ff4..b4559a90f4c47624f9ad7c5a4613c0e6af2856a3 100644 GIT binary patch delta 394 zcmexo-D|`9nU|M~0SK~+-esh0^npI2OzGTB|khB0+=i-?w3A4rC!EHkyds2`+q0*FYPd_+Wp zF@5q|5exN5kOJ1s+{EyY*LZzvO-J^d>`yKr%6p=X1ND61puW>-tArqh`IpHWESt)G67%_R1@C1=Ikdk&F0kpQDBr`v+xF~sYn1~Hy%H+u+ zT4KE*8J4ol)bgS}kjj1#kvjRZhz4WYJcCXteLrq>8ZC^OA?cEQo%mC#Z^$0 zUyxcT##*laG9n!?O-7ibCqWdvno diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index 8b79fea..6bb6388 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -57,6 +57,7 @@ INSTALLED_APPS = [ 'django_countries', 'django_celery_results', 'django_q', + 'widget_tweaks', 'easyaudit' ] @@ -69,13 +70,13 @@ SITE_ID = 1 LOGIN_REDIRECT_URL = '/dashboard/' -ACCOUNT_LOGOUT_REDIRECT_URL = '/' +ACCOUNT_LOGOUT_REDIRECT_URL = '/' -ACCOUNT_SIGNUP_REDIRECT_URL = '/dashboard/' +ACCOUNT_SIGNUP_REDIRECT_URL = '/dashboard/' -LOGIN_URL = '/accounts/login/' +LOGIN_URL = '/accounts/login/' diff --git a/recruitment/__pycache__/forms.cpython-313.pyc b/recruitment/__pycache__/forms.cpython-313.pyc index 4fbce267b203c37d43e2db56ca6e4c7c4e9904df..c5aac7d51f74d21761843a93553a9835b6a5e428 100644 GIT binary patch delta 21 bcmdn9gK^IeMy}7iyj%=GaLj8Xmr)D=P`?Ic delta 21 bcmdn9gK^IeMy}7iyj%=GP^G((%P0l_PG$xC diff --git a/recruitment/__pycache__/models.cpython-313.pyc b/recruitment/__pycache__/models.cpython-313.pyc index 2b31f3212ec593f087f87b77651a59e958346d71..2cf23575edabd6a12a886fa3b2f2b6de37772813 100644 GIT binary patch delta 3308 zcmaJ?3s98T72fj$KOi6=%low~u(&)HR30t}f(RB(Fz|yJBNb5=*s_4$UGW()j&a0k z6gXOKm8vsFjjw3_)@Wj#NfTx|NmYnZaGTEHM6D*B`f8fa^kMJ0=(=KCW;k=s`Of*y zx#yny@10TU+uKsWkE1d*%7iLV{1pF8iT?w)15BQ5 zLY*fjEDeJ??06+SN|{(UQLj+~bATu`8YRf=$pY&?bp$RXbG0*Bn>P2p%3r*s=vEy=+T_`cfaa0Uwia|-PldFryhUYE? zr-v&mg^yJyvTjZe_3*7O!m99)_{oYv(JN#1jjq^Kv&<5B>})z&^fNrog{PZ6SmIa* zHDS^iRqS)33|FAZ?8TB^BFc2(M3n4m%I77fELS8k8F*yn3oT7?;lwog|1eE;MG}+o zC8lXEoS0HMzJMwP@WU(FeBkNaBbLKYujKGm${1&xxV;$pX#KKyMlR2oBGx?ID~}_M zH0&g;AQEr#H%Sxn1OD=W%h~E3JTr|R>@M$=-6Qes3!2fB{fQjzxQbI3$}ouYLlb=)J@}i4Oi03x zU1|95!cg>%3iVVZGvI?=TB#i{J}w-e6oz(XYMxDp7xq{?ZH~pEvsvv9T}PA7?r3PX z*maH;t6jIOp}o=C*x;~`rgN}{cDvPK?ci#MXvGLlX?d$rX6gKs!}d{g+qrOhG+P`_ zFTv!TsMM6qDXUk?s%pzBOUsNB+t5WZz8$v4`mthkQ#u_pkXy@B>l->7thR=wHp|lh zXI$yCPE7v|-_Oh2t9bfG&1hiAj#WEWjc8+g3I_e-Mzs;UD|;)4wW&ke)M2e@NNYNs zl=flDnUq1TX;7O#tSuhW7H{<(2@Dwy)DH#fhXV~mfrbNXPV27*<_!Aes6*3#3y_C9 z^OGQ7J)d6)P=LLwwdgnFJHKK&b`P)1a#bk&P?|KCJB;4UlUzR~QLKJGb2dOJuILTL zq!I&UVtL7FFJ|I__^*;In1`lWU&T)$qC67$BxpdagtT%JuVGASI^4#J(#p6;{u=Gn zk>Hp;&4WRPk+9gFg(JZs4}HDDE0eBtH z%$*^(5nUTTp4$&?YVW+q9vAO5uFT zHS9|gB3n<|MFK>#o5>s`aUTC%Um%|)nRAhKm|O-RkMqDs45$;^8UO~wWp%fQ0OY9s z9X5bFIAO&f;UeB$aXjjOPZv+J1h|V=SR^QTk4=;5o6B-gZ$HL~F4+$Ne;Vy80pKAn z?>r%kX7j`_OjWX4uK*CEBdwZYs``G{HEx0eXYVQVFf=9TcA_DyJ9> znn2WUON*D^s*ruCybs21S`I$y_D#1v;Fjvx!q+bZcWy<`L%b5|c4pvcwjS4ZuY)-J zTlZ!7l{&DkUY5ftIRaCA8=_65W|0uisib9-$f1_Q@Ir48WZb-L*Kk2Sh)n>+s?V-15L8H6xh^IX{_1brlIJi3@zLXfQ(_IBv80%p5Z5F$o|FkeY0f`v2C(m1qxNK~uY2;2*TlZ`O zNW99b( z)Bh@e%J)Vtp8XV10t?Ku+HpYn!FqYOytred_v%W3hxob7L^;urJ;?P=b~t z!D`yUYm(eeU>(YbtlqxV@GZb6<_O$==&GNKk-yWbH$t%M-ONPVFsJ{j4%TXIZ?11_ zSYu~PDD7ztu3f{SckAV)ImY^6fzh{8``2Bl{@|#q31I8UI^SQ-U zBd~Pr>5No2AJfAtZgpwFz9FF}(MhDEq$Q9LpY7SiB&wB>_6Y&v13!<cw6E*h(&HCK5>(6q%9K15_I_FR4i;(W8bfU F{{Xa}UGx9| delta 2902 zcmaJ?ZBUfg72fj#FA!LZBJQ#%u*+9gVHZJ^)m=1vSTR6^U6LVeT~^tZ4X{h@@)6S{ z7&U1kB-8PTwlpcIttNJ>fp?4<<2aL#ADyN#fi(TG9Vf{oW{908q|J0Pown&ccjN1- z)9#OF&biMy&$;*9bKkvwP5S&}N&9!L_AU+oeOdQq&-jd%mB0Y*D|sC=w(G%-j_o>w z^Dej6q;aNI0mW%F&U9NMTjEY^%SB^xR<>5KOH}p;Hi>22U^8QEMm&q!pBVHaX{oxr z%mys;MpAaBV3(^bNiOH?YEdvtuq$q1S8&!MSe;C-CU8Ir?UAxcjaxlMwKPyRh-Smxl*0g=$6~;vG&EIP)tyB3V37lM9$ZxSqZ6a zc5KyU<`p_k&Rl1#dXXAiY?E0&kFBM1<=o&)RQ_&T0yC*v$uX@}HMmeAJ?4(M*zKeQ2N=oa*tyHPC*7HtR=r#Na zY%pk?)m2jL23e(hozrH}RDFM;tcv>^XxFR*r`@1=D1}vX&JoAixL;$? zw5Vbmotq6Bwuvuqi3_uH|DHJC!Ts7ezs6a|Hn(dWz_t`>%=Jp9Wjay$tl|M#`QfJ1 z09O3UUI-P+a)$$;65DFNORMIOn*Ed?fmsyy<_prYTE1XTW{I>kPEf5B)z$;tgEQ{s zhnth|K%EKC)J2k`N#Q~79v^nrmqQ(n*ZvGOS1BWXXF+?OhnN$Yiy|*zMQ}9d zJu=@Xm?xMcc$Y(qIgiF+1EZ5h)3!D?G}U`t9XlGFamN-%62}&b;@BUO!aJx76`5zr zyhPANaD{-h+0CM&-aPt3CGurrE-QyaUXU)w?o<14CP0~DWPJc1;q&3EZ~^-Ue`EOH z-N&^<#+*+`i8d2m;vE?13+(m=2R%V=zmMIE{X>;<8S!O!YUm6XZylZn{$Tz#0sz2a*v@`4xr!6Y#?)K5*Z5HnCUHVk?D;7tpxxGguIFnD?YCNq+$>@6tgMZay$7y(-Rz z9~|(-wc1GHaf6O+2ODLec?3LuZ~#DA_SCfmxT<{n3?JEa)Q=&&h>@{(L5GiyUxD8$ zjn8>xIfF8m;#u?>O2{-5h{ZKzl@VBQZc>L^j$}YFwjSx>U$u)zM&W7PII#}WFf?%< z&MQSn{Qx#){Mgfwv7VBws!$1<` zrHP;!9n+=IqC7PH7QlYAobaWHcPp_m{KbjKjOxLnsjfi3Z)?EU*UhTwcE%|E0SN~} zD`gk$&V>)2tOu7eb@G}d?E_LfKKK5%X=iK#7s;#tT%`dC#)m41BcIUmHm|GSKd5Z z1>je{J=X|b;?G40h&dIXC-Gv$D-we*2DX><#P#))C0Zq_2~7j76%o)Aj;7fAL)}At Nz8dzpa(t!{{tIws(8K@$ diff --git a/recruitment/__pycache__/urls.cpython-313.pyc b/recruitment/__pycache__/urls.cpython-313.pyc index cf248c2c99f116c7b0918d9251c2e4953386e35a..4f3d89756513ca97f98ee48d5564124f1e864a1e 100644 GIT binary patch delta 1302 zcmZvb%TF6u6vjQ{p*2v5(gsiLdDtGXUD99(F>PT2Hhw@z^B^_`3I^B2!z+X&wxKOe zOp-p5Rg~y%vZ+ecNFYV3oU(DHE?8AvbTLL+zL|FP+DAF+ zxrIHqUgsoAf5|+>*uq!sU+@0j@ZFoi@#V{NrOp$jm9>RFA_JW#mR8Gs7xJrxr9!@J zYlEGbZgx{E<%aHS(x`MG{l);`T zy8b6!{*Su2`0jIYndgf-7<4-u`|)v$14JTV&K-8g$TChOK_p3Jh{!N_?4E91#7hp} zrKkfOlv-Q{cYuE;IPS>zDwY_AVz{P5VHnvB5Ro*A(3cIiy81ingBHux4qfflx1i9E z8VztwlH8OxV{X>BZe_M-AI{c}lJo-xcp|wu1NxHV# zR#yggWz1|D3Tvp>0N*PbuJnr%!1|{WU^NmTs(S26yrruI%&DZ^ZMyz}>Qk!ws)G03 z9$BVMvK;N=-STgT9)_wFC=8()Y)E;6ST{T>t3D4lJ)(U9Z`C}iPBX8tOxJ@ROX`49 zM-`7Oj;?YP78!hRCu4O)U`J$c6$%+t!ubciGS-lnoZ>XqWv{}TRKR(!^ZiX$A;!~# z<;@F=f7xsW?iBx$J&?_Io-dK8uFRu2Px}pl$6m!T%z~4^^eVm)7Mx~vj#YcC;~97y zk8uMEU!XI*0gn3qZJYwfd`?H2C5(e}zLvrS3np3iyE}gCNE9B4?p-JE@iBu_4C=Lv z)jtLMr_4MpJCJUl=u&(**2kmr7F*|sq z`SWoW4cX~{N6plPm0E|Xbv)P#H%kYb>bNMp<3>UdGgp&t$kMTw7F7arD Mx^FfX=kag-4*@BKr2qf` delta 987 zcmXAoOH30{6ozM}!=n^1S_&@$=tY@xIi~9)P=c(g^AI``)H4tUa%( zsp9bOXYALVtg=^YX&V zQPm}^(u~snjIOjprM)x(#W*sn;G#@qZL<9em05{d<8D%~^TIms;UE+v$VMv)HE26r z*8NH7Pioh1KyeZ|RIouEp2~Ka>bz8Uxzwx&hG1Yw8@&g``=~_)pJWf7^jmJiloZ04 z(o{}+0>Gu%s^_7&fP(ZOL=GzGL0APPh1~Ik^1I^3vB}}Z3{Z|!Zc-jNi0=uF&nX+7 zL>Y6+n3GNkbIsX^`NY|R`PQjmexdwFDY=NI%O!Qwi!AYEA=cfnZf=FoGh~B5qRcN{3|ReGSjq{$?2ID zm|4L~hzV14395irnQBb{KRnIG$1GbHKtoe=aD!!W1Kr|?md)$gBFq-CLtId)!z}?N O3}gvgrouG-#{L7pfh}YJ diff --git a/recruitment/__pycache__/utils.cpython-313.pyc b/recruitment/__pycache__/utils.cpython-313.pyc index 56997da8f0c060ea9aaf882ce44884eacbf13b50..fbf2b0d82f84aabde430b7ecca08c22bb8f46092 100644 GIT binary patch delta 1511 zcmbVM-A^J{6ral=A2Kt$%Bp-UFl(&}5o#3@b+v2KsN>p2g-(Lq50>Jfv^dxSsD%Y_8WS6oW;)r zW4~hzkzO532p8UVAVjTQHG7ci|}EsTy8e{){I=-%W3 zdhuSM`+O_?n-D7q05t{&F|@LHeNwX(KbJ-N)Q!bLaEE@IhQ`E_%LNQ*|1k;+8%JAs z8cG#z51V|NKBRyu9)ZM*O+e|on_M9~RK%=O7$617Hd?xpbl zYKP8kuEw(5D*aJ9y+LnqTsFzC(KX=9_o@-`lfi+4Z;1Xlm!%W2WC}vFX*$7WgP)}%s&*nUQ2{X^(;Su(Vq@P$S*ZlG| ze>tU)??0NY>dE4|tnWD(KhpP|Sl=xVRz_yzkr~B0TcnOn)`LOW^wyId#WY;CyCu){ zf^45GesHS8oSurqD?7ZUwet2)4_`b~9J57cFL+E3Rp?=v9xmHnMk-T5c`B&Ti$&&$ za8&KHb1`ediCw5>SDiUnqG@z{I4;=LhzZ%EV2iZ_JFJyqh$<6FD8_7-0eosSoG z7bM#x>c1pUElHEh(sD?$gpY_1tIh$*ACb)~60uSx?FWoZzFq23$l()M7TgV1JN@NI zCBVo5M)J>}V&EpQRc(V6+mLJid$)>2JjSBbSeG0!q=l0Z00v8u@$?OJqNX!wSzkaXJFcm{XOwBO4kzUQExc7t( cc+=<~DRU@}Ge2DZ^QsQ?oLzB&`=Ad08)B!4r~m)} delta 89 zcmaDqpYh#9M!wIyyj%=GP^J4WBi?f&p9G`JMs*%WMw!Vbj20~EoSL$m%NV;{*^ABt p6=`a2{_ZQzI9b?VoQs2zfdPmZiq}py@n6mLS%!^KyeI)E002Mv7Cry~ diff --git a/recruitment/__pycache__/views.cpython-313.pyc b/recruitment/__pycache__/views.cpython-313.pyc index 251fa3edc1ed46e09f8c353604415554a0566b55..433256d66faafab84c5b6634d60ef08847543e98 100644 GIT binary patch delta 16020 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 delta 15305 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 qb!yX;uyu?n7X5!m!Yxk# diff --git a/recruitment/models.py b/recruitment/models.py index 3efe497..be2c445 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -273,12 +273,12 @@ class Candidate(Base): CANDIDATE = "Candidate", _("Candidate") # Stage transition validation constants - # STAGE_SEQUENCE = { - # "Applied": ["Exam", "Interview", "Offer"], - # "Exam": ["Interview", "Offer"], - # "Interview": ["Offer"], - # "Offer": [], # Final stage - no further transitions - # } + STAGE_SEQUENCE = { + "Applied": ["Exam", "Interview", "Offer"], + "Exam": ["Interview", "Offer"], + "Interview": ["Offer"], + "Offer": [], # Final stage - no further transitions + } job = models.ForeignKey( JobPosting, @@ -412,13 +412,13 @@ class Candidate(Base): # allowed_next_stages = self.STAGE_SEQUENCE.get(old_stage, []) # return new_stage in allowed_next_stages - # def get_available_stages(self): - # """Get list of stages this candidate can transition to""" - # if not self.pk: # New record - # return ["Applied"] + def get_available_stages(self): + """Get list of stages this candidate can transition to""" + if not self.pk: # New record + return ["Applied"] - # old_stage = self.__class__.objects.get(pk=self.pk).stage - # return self.STAGE_SEQUENCE.get(old_stage, []) + old_stage = self.__class__.objects.get(pk=self.pk).stage + return self.STAGE_SEQUENCE.get(old_stage, []) @property def submission(self): diff --git a/recruitment/urls.py b/recruitment/urls.py index 30dcb9c..4aefef5 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -68,6 +68,8 @@ urlpatterns = [ path('jobs//candidate_exam_view/', views.candidate_exam_view, name='candidate_exam_view'), path('jobs//candidate_interview_view/', views.candidate_interview_view, name='candidate_interview_view'), + path('jobs///reschedule_meeting_for_candidate//', views.reschedule_meeting_for_candidate, name='reschedule_meeting_for_candidate'), + path('jobs//update_candidate_exam_status/', views.update_candidate_exam_status, name='update_candidate_exam_status'), path('jobs//bulk_update_candidate_exam_status/', views.bulk_update_candidate_exam_status, name='bulk_update_candidate_exam_status'), @@ -100,7 +102,8 @@ urlpatterns = [ path('jobs//candidates//reschedule-meeting//', views.reschedule_candidate_meeting, name='reschedule_candidate_meeting'), path('api/jobs//candidates//reschedule-meeting//', views.api_reschedule_candidate_meeting, name='api_reschedule_candidate_meeting'), # New URL for simple page-based meeting scheduling - path('jobs//candidates//schedule-meeting-page/', views.schedule_meeting_for_candidate, name='schedule_meeting_for_candidate'), + path('jobs//candidates//schedule-meeting-page/', views.schedule_meeting_for_candidate, name='schedule_meeting_for_candidate'), + path('jobs//candidates//delete_meeting_for_candidate//', views.delete_meeting_for_candidate, name='delete_meeting_for_candidate'), # users urls diff --git a/recruitment/utils.py b/recruitment/utils.py index bb51ebf..b7b19a6 100644 --- a/recruitment/utils.py +++ b/recruitment/utils.py @@ -579,3 +579,38 @@ def get_candidates_from_request(request): except Exception as e: logger.error(e) yield None + + + +def update_meeting(instance, updated_data): + result = update_zoom_meeting(instance.meeting_id, updated_data) + if result["status"] == "success": + # Fetch the latest details from Zoom after successful update + details_result = get_zoom_meeting_details(instance.meeting_id) + + if details_result["status"] == "success": + zoom_details = details_result["meeting_details"] + # Update instance with fetched details + + instance.topic = zoom_details.get("topic", instance.topic) + + instance.duration = zoom_details.get("duration", instance.duration) + instance.join_url = zoom_details.get("join_url", instance.join_url) + instance.password = zoom_details.get("password", instance.password) + # Corrected status assignment: instance.status, not instance.password + instance.status = zoom_details.get("status") + + instance.zoom_gateway_response = details_result.get("meeting_details") # Store full response + instance.save() + logger.info(f"Successfully updated Zoom meeting {instance.meeting_id}.") + return {"status": "success", "message": "Zoom meeting updated successfully."} + elif details_result["status"] == "error": + # If fetching details fails, save with form data and log a warning + logger.warning( + f"Successfully updated Zoom meeting {instance.meeting_id}, but failed to fetch updated details. " + f"Error: {details_result.get('message', 'Unknown error')}" + ) + return {"status": "success", "message": "Zoom meeting updated successfully."} + + logger.warning(f"Failed to update Zoom meeting {instance.meeting_id}. Error: {result.get('message', 'Unknown error')}") + return {"status": "error", "message": result.get("message", "Zoom meeting update failed.")} \ No newline at end of file diff --git a/recruitment/views.py b/recruitment/views.py index 5c89c40..58b4be2 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -32,6 +32,7 @@ from .utils import ( create_zoom_meeting, delete_zoom_meeting, get_candidates_from_request, + update_meeting, update_zoom_meeting, get_zoom_meeting_details, schedule_interviews, @@ -171,39 +172,14 @@ class ZoomMeetingUpdateView(UpdateView): if instance.start_time < timezone.now(): messages.error(self.request, "Start time must be in the future.") return redirect(f"/update-meeting/{instance.pk}/", status=400) - result = update_zoom_meeting(instance.meeting_id, updated_data) + + result = update_meeting(instance, updated_data) if result["status"] == "success": - # Fetch the latest details from Zoom after successful update - details_result = get_zoom_meeting_details(instance.meeting_id) - - if details_result["status"] == "success": - zoom_details = details_result["meeting_details"] - # Update instance with fetched details - - instance.topic = zoom_details.get("topic", instance.topic) - - instance.duration = zoom_details.get("duration", instance.duration) - instance.join_url = zoom_details.get("join_url", instance.join_url) - instance.password = zoom_details.get("password", instance.password) - # Corrected status assignment: instance.status, not instance.password - instance.status = zoom_details.get("status") - - instance.zoom_gateway_response = details_result.get("meeting_details") # Store full response - instance.save() - messages.success(self.request, result["message"] + " Local data updated from Zoom.") - else: - # If fetching details fails, save with form data and log a warning - logger.warning( - f"Successfully updated Zoom meeting {instance.meeting_id}, but failed to fetch updated details. " - f"Error: {details_result.get('message', 'Unknown error')}" - ) - instance.save() # Save with data from the form - messages.success(self.request, result["message"] + " (Note: Could not refresh local data from Zoom.)") - return redirect(reverse("meeting_details", kwargs={"slug": instance.slug})) + messages.success(self.request, result["message"]) else: messages.error(self.request, result["message"]) - return redirect(reverse("meeting_details", kwargs={"slug": instance.slug})) + return redirect(reverse("meeting_details", kwargs={"slug": instance.slug})) def ZoomMeetingDeleteView(request, pk): @@ -1020,7 +996,7 @@ def submit_form(request, template_id): address=address.display_value, resume=resume.get_file if resume.is_file else None, job=submission.template.job, - + ) return redirect('application_success') @@ -1125,17 +1101,6 @@ def form_submission_details(request, template_id, slug): def schedule_interviews_view(request, slug): job = get_object_or_404(JobPosting, slug=slug) - - # if request.method == "POST" and "Datastar-Request" in request.headers: - # form = InterviewScheduleForm(slug=slug) - # break_formset = BreakTimeFormSet() - # form.initial["candidates"] = get_candidates_from_request(request) - # def response(): - # html = render_to_string("includes/schedule_interview_div.html",{"form": form, "break_formset": break_formset, "job": job}) - # yield SSE.patch_elements(html,"#candidateviewModalBody") - # return DatastarResponse(response()) - - if request.method == "POST": form = InterviewScheduleForm(slug, request.POST) break_formset = BreakTimeFormSet(request.POST) @@ -1193,16 +1158,15 @@ def schedule_interviews_view(request, slug): # Create Zoom meeting meeting_topic = f"Interview for {job.title} - {candidate.name}" - start_time = interview_datetime.isoformat() + "Z" + start_time = interview_datetime - zoom_meeting = create_zoom_meeting( - topic=meeting_topic, - start_time=start_time, - duration=schedule.interview_duration - ) + # zoom_meeting = create_zoom_meeting( + # topic=meeting_topic, + # start_time=start_time, + # duration=schedule.interview_duration + # ) result = create_zoom_meeting(meeting_topic, start_time, schedule.interview_duration) - if result["status"] == "success": zoom_meeting = ZoomMeeting.objects.create( topic=meeting_topic, @@ -1212,16 +1176,19 @@ def schedule_interviews_view(request, slug): join_url=result["meeting_details"]["join_url"], zoom_gateway_response=result["zoom_gateway_response"], ) - - # Create scheduled interview record - scheduled_interview = ScheduledInterview.objects.create( - candidate=candidate, - job=job, - zoom_meeting=zoom_meeting, - schedule=schedule, - interview_date=slot['date'], - interview_time=slot['time'] - ) + # Create scheduled interview record + ScheduledInterview.objects.create( + candidate=candidate, + job=job, + zoom_meeting=zoom_meeting, + schedule=schedule, + interview_date=slot['date'], + interview_time=slot['time'] + ) + else: + messages.error(request, result["message"]) + schedule.delete() + return redirect("candidate_interview_view", slug=slug) # Send email to candidate # try: @@ -1354,9 +1321,9 @@ def schedule_interviews_view(request, slug): else: form = InterviewScheduleForm(slug=slug) break_formset = BreakTimeFormSet() - print(request.headers) - if "Hx-Request" in request.headers: - form.initial["candidates"] = [Candidate.objects.get(pk=c[0]) for c in request.GET.items()] + if "HX-Request" in request.headers: + candidate_ids = request.GET.getlist("candidate_ids") + form.initial["candidates"] = Candidate.objects.filter(pk__in = candidate_ids) return render( request, @@ -1675,7 +1642,6 @@ def candidate_screening_view(request, slug): return render(request, "recruitment/candidate_screening_view.html", context) - def candidate_exam_view(request, slug): """ Manage candidate tiers and stage transitions @@ -1728,7 +1694,6 @@ def candidate_update_status(request, slug): job = get_object_or_404(JobPosting, slug=slug) mark_as = request.POST.get('mark_as') candidate_ids = request.POST.getlist("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") @@ -1739,14 +1704,55 @@ def candidate_update_status(request, slug): def candidate_interview_view(request,slug): job = get_object_or_404(JobPosting,slug=slug) - if "Datastar-Request" in request.headers: - for candidate in get_candidates_from_request(request): - print(candidate) - context = {"job":job,"candidates":job.candidates.all()} + context = {"job":job,"candidates":job.candidates.filter(stage="Interview").order_by("-match_score")} return render(request,"recruitment/candidate_interview_view.html",context) +def reschedule_meeting_for_candidate(request,slug,candidate_id,meeting_id): + job = get_object_or_404(JobPosting,slug=slug) + candidate = get_object_or_404(Candidate,pk=candidate_id) + meeting = get_object_or_404(ZoomMeeting,pk=meeting_id) + form = ZoomMeetingForm(instance=meeting) + + if request.method == "POST": + form = ZoomMeetingForm(request.POST,instance=meeting) + if form.is_valid(): + instance = form.save(commit=False) + updated_data = { + "topic": instance.topic, + "start_time": instance.start_time.isoformat() + "Z", + "duration": instance.duration, + } + if instance.start_time < timezone.now(): + messages.error(request, "Start time must be in the future.") + return redirect("reschedule_meeting_for_candidate",slug=job.slug,candidate_id=candidate_id,meeting_id=meeting_id) + + result = update_meeting(instance, updated_data) + + if result["status"] == "success": + messages.success(request, result["message"]) + else: + messages.error(request, result["message"]) + return redirect(reverse("candidate_interview_view", kwargs={"slug": job.slug})) + + context = {"job":job,"candidate":candidate,"meeting":meeting,"form":form} + return render(request,"meetings/reschedule_meeting.html",context) +def delete_meeting_for_candidate(request,slug,candidate_pk,meeting_id): + job = get_object_or_404(JobPosting,slug=slug) + candidate = get_object_or_404(Candidate,pk=candidate_pk) + meeting = get_object_or_404(ZoomMeeting,pk=meeting_id) + if request.method == "POST": + result = delete_zoom_meeting(meeting.meeting_id) + if result["status"] == "success": + meeting.delete() + messages.success(request, result["message"]) + else: + messages.error(request, result["message"]) + return redirect(reverse("candidate_interview_view", kwargs={"slug": job.slug})) + + context = {"job":job,"candidate":candidate,"meeting":meeting} + return render(request,"meetings/delete_meeting_form.html",context) def interview_calendar_view(request, slug): job = get_object_or_404(JobPosting, slug=slug) @@ -2230,12 +2236,12 @@ def reschedule_candidate_meeting(request, job_slug, candidate_pk, interview_pk): }) -def schedule_meeting_for_candidate(request, job_slug, candidate_pk): +def schedule_meeting_for_candidate(request, slug, candidate_pk): """ Handles GET to display a simple form for scheduling a meeting for a candidate. Handles POST to process the form, create the meeting, and redirect back. """ - job = get_object_or_404(JobPosting, slug=job_slug) + job = get_object_or_404(JobPosting, slug=slug) candidate = get_object_or_404(Candidate, pk=candidate_pk, job=job) if request.method == "POST": @@ -2253,14 +2259,15 @@ def schedule_meeting_for_candidate(request, job_slug, candidate_pk): if start_time_val <= timezone.now(): messages.error(request, "Start time must be in the future.") # Re-render form with error and initial data - return render(request, "recruitment/schedule_meeting_form.html", { - 'form': form, - 'job': job, - 'candidate': candidate, - 'initial_topic': topic_val, - 'initial_start_time': start_time_val.strftime('%Y-%m-%dT%H:%M') if start_time_val else '', - 'initial_duration': duration_val - }) + return redirect('candidate_interview_view', slug=job.slug) + # return render(request, "recruitment/schedule_meeting_form.html", { + # 'form': form, + # 'job': job, + # 'candidate': candidate, + # 'initial_topic': topic_val, + # 'initial_start_time': start_time_val.strftime('%Y-%m-%dT%H:%M') if start_time_val else '', + # 'initial_duration': duration_val + # }) # Create Zoom meeting using utility function # The create_zoom_meeting expects start_time as a datetime object @@ -2307,7 +2314,7 @@ def schedule_meeting_for_candidate(request, job_slug, candidate_pk): }) else: # Form validation errors - return render(request, "recruitment/schedule_meeting_form.html", { + return render(request, "meetings/schedule_meeting_form.html", { 'form': form, 'job': job, 'candidate': candidate, @@ -2322,7 +2329,7 @@ def schedule_meeting_for_candidate(request, job_slug, candidate_pk): 'duration': 60, # Default duration } form = ZoomMeetingForm(initial=initial_data) - return render(request, "recruitment/schedule_meeting_form.html", { + return render(request, "meetings/schedule_meeting_form.html", { 'form': form, 'job': job, 'candidate': candidate diff --git a/templates/base.html b/templates/base.html index de49a92..ffe4948 100644 --- a/templates/base.html +++ b/templates/base.html @@ -12,272 +12,18 @@ {% comment %} {% endcomment %} - + - {% block customCSS %}{% endblock %}
- {# Changed container to container-fluid and added max-width-1600 to inner div #}
- {% comment %}
- - info@kaauh.edu.sa -
-
- - +966 11 820 0000 -
{% endcomment %}
{% trans 'Saudi Vision 2030' %} @@ -297,7 +43,6 @@