From b37af920ba4f6171fd9f6f80f3e577226ff14acd Mon Sep 17 00:00:00 2001 From: Faheed Date: Thu, 30 Oct 2025 12:09:52 +0300 Subject: [PATCH 1/3] small fix in the dashboard --- .../views_frontend.cpython-312.pyc | Bin 45108 -> 45109 bytes recruitment/views_frontend.py | 21 +++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/recruitment/__pycache__/views_frontend.cpython-312.pyc b/recruitment/__pycache__/views_frontend.cpython-312.pyc index a801254cb2b5fb52676fb58b22b57d9984b40ab7..7c33a4c37f1a51effb9f4a8f0a9038edf0a447de 100644 GIT binary patch delta 7846 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 C7zHf= delta 7688 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 Date: Thu, 30 Oct 2025 18:00:51 +0300 Subject: [PATCH 2/3] updates --- .../__pycache__/settings.cpython-312.pyc | Bin 8201 -> 8201 bytes .../__pycache__/models.cpython-312.pyc | Bin 83794 -> 84639 bytes recruitment/__pycache__/urls.cpython-312.pyc | Bin 18794 -> 17595 bytes recruitment/__pycache__/views.cpython-312.pyc | Bin 142367 -> 129423 bytes .../views_frontend.cpython-312.pyc | Bin 45109 -> 45495 bytes recruitment/models.py | 26 + recruitment/urls.py | 29 +- recruitment/views.py | 504 +++++++++--------- recruitment/views_frontend.py | 11 +- templates/agency_base.html | 152 +++--- templates/base.html | 10 +- .../recruitment/agency_assignment_detail.html | 150 +++--- .../recruitment/agency_assignment_form.html | 154 +++--- templates/recruitment/agency_detail.html | 6 +- templates/recruitment/agency_list.html | 6 +- .../recruitment/agency_portal_dashboard.html | 55 +- .../recruitment/agency_portal_login.html | 9 +- templates/recruitment/candidate_detail.html | 18 +- .../recruitment/candidate_exam_view.html | 6 +- .../recruitment/candidate_hired_view.html | 4 +- .../recruitment/candidate_interview_view.html | 10 +- templates/recruitment/candidate_list.html | 33 +- .../recruitment/candidate_offer_view.html | 4 +- .../recruitment/candidate_screening_view.html | 6 +- 24 files changed, 649 insertions(+), 544 deletions(-) diff --git a/NorahUniversity/__pycache__/settings.cpython-312.pyc b/NorahUniversity/__pycache__/settings.cpython-312.pyc index ef70d24783e736e696063246d75a7968373dc1de..48c95dffce7272b7eb4ddf644f6b5895f5be427e 100644 GIT binary patch delta 20 acmeBl=yc#d&CAQh00gHpnKyF(lLr7e;RT!k delta 20 ZcmeBl=yc#d&CAQh00KTt8@d0<0{}9p1hfDE diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc index 098b384aba682868a1ff92664be2dddc244facb3..fff953ce715002fc65889d40bd9cc5bdd4293059 100644 GIT binary patch delta 4875 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 delta 4155 zcmZ`+dtB627N7GQegn>kA`UVjZ(j`Y21P&&@`0pM8jFP*j56w=Jp9dokJQ8-wrZ&z z@%nycu_|rYFZ^k`YGtlgptb92qi$+yqN`RuNHgu8i-jLQpPfIxbM8H_d(OG%-uqi$ zBY*Rj+~G@aZ=;U?;+IQ~SDLqX7?%U*)u}n(c^Ti(O^1RPp3_;!t0g1*SS4!#?jK^t zALjhk*X`qQ7AkgUWvRWe!lpRw#cJitI{_1`yFouSzdFqz-;~U9UvWD7TPr>TpWhx`+t_#BTd86w>SHFW7pF#IHOxhuH$ z?Qf);626U}27B@ucf0YnvSLTE&1F}1a2>0p4B}BJCY-t~Rr*!Jx@9R+I|f#&fOVJXAlwuV{()2q;BL zJb{eT3I?Ze)rziJcZfBQW(O0b(9nyP+n%5!fiJ-m#EmA1A-GO*1P9dHEA9ha$NKpJ zYKPT@02i@rja%-{m11%D+G`+V!VWV&yDkWluyma*P^bE*9QHS>I%2~HNlxT4Pvh-PJK#Bd|D8RMua;~c4^V(j z@1}aBa3)q)b;0oWV&MgR_PzcPg^S-Cl$b>3W67O}w=WI*6Fe4RG|dsDprQ6th{ms5 z0@QQ0Dd0(_^OP72+Y$t^n6c$&`6)hAipg6WK|yKT-(di5-xdp%cxGGw*eJ52Cw~c) zGGSH(pF<2$6BM~1?f@KC*Tz-dg~If^H9>CUc!Vx=M) zAt0g|PgV=?;LaY_0YnZXI7QKoC29=8iv)!PMFci9?}{+y5=k&q&D=E&q+F`JMg98Y z*8yVG=l6^T!(`5F(c`Xv{Ad>G6PcUBC*597moi~_HweVzd;O&cdi-(k$iV-fdmOVb z9H!u$eF4yjEB2+pS@r0?=@R#o^XX`iE4fL3-16BWQx&Ha5t4?sZv(LAKo#irIt;%wncX54)+oEPf!!5a{CmM4Puc{*php~}hs zbYWqwE!7YIcPI(^Vc217_cjY6#Hp0N(PU8Muz{*^7MD&kWA))DAQ|63Jpbj#f^8rb zQ5uaXkz5gsD1|V1lo&I}YFj^u{ukb75o0TfR1@`Bd(g@i0=`e@_AhzLYLZ)pSB@7M))2Wy9dV*B7{1`EQhMX!Z>LK= zL^H_iIkypexcBo@=%Tw0MyYr*)6-BxO3U%vbE5)PA{z+Aq(;;(f{*d(?<3?7Ie7x7 zeqRi`@ju_c?5F4BncgGq&WiFfr#)<-&1F+oV#)a)hBrw`hc)NxAsYu@$dP{Yz@--+ z$e;0fomz9T1bjXrk=+Esmx>)Py}%Dj$E8_5ZFLk!_ins(sh5T1-Cp*x;)-%dnagb` zwYw&j7b~YQxj8)aAlZrLm2!LA6{TD$EVVnGwuyFS6EWUFS99->LqtAS!+M${_?P<6 z<~1PIOE}xpqRzf@7+@b}Up-+6mbnC_aTLzF7WYRgk8zbSJbZ1Ep^>&n6Q*2G@HoOr zEI%1&oJHj%=%g;WeoZ#~m%N<8S8qibPjk}BO*+$XW1bnmx)lfK@ZPOPSd3rXZh|Ga zvLyu;sb9C00k2Z#ofqY1u2G`CbH@|B9-c&(Synu6Gxyyfs8U<*4gk1<>GvkX47K)N z5e>ZVS3@-}xPKUKi?*EjAQ~2`%O3bhp;u@rUbMco1il2~4!T8*n_N7AodO7iC0YwW zFqp2>{GZIuEKv`AU^R2-;fQoaV#EC*f{pQjP}sm`dBDepn?j!4vS(!I3GZrEGMK?H ziRO2cyZc1VBkCMmZ-4;9FMMKV2*NG1iN6L4ot5KC9j>j~X#;eHaI&`r+}@QH^hhY2 z;IL0AcIFMX>J?H`Zt`{u01Hd-f}!xCHros8cm`OM5u##&AN+%@o+4bg7k}!M*&J1N zg;L-)mf9xU3vJVEid`|#1tGI}MyP;&?1mBEwLZ23&yg!}d5O}t-Q1ViCno4@ZOsn} z#qqe@-ZrPxF|o{ETqqtkBIl8`PmA%E z5j?hgPm&x#Pwn;gph(h5abIY@9l>7@3t5yeWWohD*%#uai>>F9*5C`B!IP?yYc*m* zz8TXOm@sawj3swE;Ns8x41V0xU6}=$;TCvkzJA~f;Lmz=fq~%3O1i*VJ{sl^PeOaP z$RFw&}h_@^uDj zox1V@#H3SDv?bkMwkZ{pY~$=Mr8^gQ^7D*e6m#c3yehcZ%-(EiFyDVigLq6m+4&$y zhVCpN7_v=Ikk2TB?rd@}41sX%qhLq`AM&j9B#0x3XSYM3A4IaGP#F8j1tLylk>9pr z2atle;Dr&PPxV$32>gjFLXD>yhS9LK=oC=f^&%=P?if+wV3G?b=&9$nm3Sg+br@G| zF?J-HpixT?gV`W;)Qii7eG?9v;Xb+1vBeQk)Os>VikY=UKyOH8G5i4^{^urkx65;s z+6PnXv^Kk$mefa&p}YKzN#ukeiEZn~Yn-J0*bRd85W~ECL4T8uM1`wlEx#A^0vM&u ziG&1@Gq_Fx`^X9}LKN#91$`p>lc^^O=!SK-pH{9+c9c!zT`UGk%8jWs zL6D(cj)KVmsak$C?2v+D$Yn0&RD2>B`EhSosvP!dPF}9aI0yk9%Z`KIVZTrYTwIWU zMwFLUOtHJ{#maZ2J&m;AVr$}HGnnYO902pR*W&r^^fGaNu9B&z_n|z3yI;UQNdQOE zpV?|{^h~lXYEKGE1`Wlg5O4SWG)!e<6L~j_z(%r{5}}VVn_?muukB8Ru^?sZvHplf zOH76ndT3&%zEC9RbAAo0=nK(Ofu4;>2MaD7>8E|!7dnAFhBH^Pv#GEoM5Ml$M>4Zh zZn?`b!7<+Ea+H^a<;|$DD?hNsY4ED)0xj+>g527WbO6X>q5WX4T*}QHV4M3vi=l#( zN+jEu0ZGuvj%7e|5cR0lpyUxJk3yM9)I3U>i~2Nx-RKXIa8wI=61*VG#kss2T`qfR zh0Cc3ZO=!vhm(m`ZL%|spq3l^qr)4SJ(D-tF}5iaiXQnK5Vv~)NsC2@1GXbAqOIpD zSbP=)!Sif*7WkVckgRZYot0-n;LviK5bc*pdZP1$ds^}%O;<_7=>(4*p{*(4jHTMY zvtScQd3t&QXiEk_9`Jp3VIaKG`UM~<;vL$AKV1!E%Laj2x@cfq2SHj?+s+Y(gSbwG zZ0p;AlJhjJE=MOCu83%Y(^}kM7%Jt9PiV2NLa3199hga!c(ICCEIl*DTTq!v+Tz(F wt|D*|ArJv?vKvESLC0|((07T)O&$H;%x|YH%Hb#G?=sV3z5oCK diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc index 05d0cffb59925c22948b312317ac76adebdb9600..e468e4d52dc3c34678d7d0599abccc7f66551ef7 100644 GIT binary patch delta 2924 zcmZveYfM|o6~}$;F_>gYQow|#Ap{KO4aOL-&BG7gPZJaKG!L8i(?EiO#O;?Zm9|pY zskD4ptyt|BZ+F!?;tOikDqU@)?NfKOSDLolNLy9yMyqr?k*coRm8#x52en-#mOZ+E z|Ndvr%$d1!@8_=zKmLx8{LAFzODg&E*r!=KKTgh6FQ-2&`5rp^EvgVSnq?H5>F(>Fpzr;p_cZX| zv7lCkgy2XBEx*)>f1&%cfN(tND?xMi{-wWZuAt?&{;#{U$5*-pCF9j=%|gOi|FwS# zcq=6>^}`oZJjUxO*M$$yzMXQWmZ$O;Wh&P{d%^ZJq*;E-ry0?w$-d9|^*j9f8ISqv z3o&2FF<)@sm-^&{vAqa0J%1-N12q@U&8!EPf-^gzD*N{8?n0B&G*^W~JK;#h_S{Bz zX?>yU_t*dH3UCs*AZHml-%rCy=4V3W>>uU^AvNV|IB;hvboZ>nk}6m?nUOjRn~XeSS%$O*%51=xL^jWSZiyDEx8&zulNE1n^H9 zGjg~qMRtivIVT}%s3eBU_=JRj(C!rB`BY*=89L%&{Qb5pYiq;n{r)aD@@?M)Zu4M znRvjaRa@j3n-ckkO^N)GExUj_>^x6Z+(2u*q{b_bh9$Jk9Gp4vC$?OjOW?x&x;;!v@DS*KGiP z;bI_qTGLBvdd1;62?g0G&?$u;<(_D1MhVF%5vy7x)XD+?r}35}RW}1-9>3`N)@{Yol1wNK5)iNuPKtD4`HrfrwSW1!AFv7WR@tIrWN!R@oZh9sG@EeRLPZ z9q?AhwY1tvs+}JhB;;pz0q*fZW<0Ra)N+zqE}Ah(jZE zXp0PO$tfa&kEa+>vq^AG;juP@E&yU0f3wY~n*lM4e;|uF5cBvCvTXsxB33(%x+M^s zxZ0UExdkE&Ict@Adx_aA`gujSf$k{uDCa@wjg8}tm`y@fwhQDAC)p2tRBs}B)5Yz1 z7vLU#+o{(@Kd^8}T3D$!G_`9x)bEzM%xbmU{5I6{dMW=_fxSJGr zi$nY%7y{}AYKqxuSwAW37k!HoT4KXM$ADJEc4<{7sp=F5cH^t5f9w2h0i zd@Uz|3e&j8ovxb!6~qJX^wAKAWhL+^`$34N7nAhj*qVgwYz5dVbeetQqq)^2w>ln` zPzPHBxDGJ?w1^g1NP*>Co_jCbkmvr1I|t6>F#go7k8Xp12mEC*AFXhc3VAb5NhrW} zfgS=ioN8&lo#fj^*SJ#3dqD34UialcQyo4556lklKF9*L%hlqJd^z&y(o0ji# zhZAsV%tOn&Nx58fe!RK>yI&sc0oslqdU9JjK=gvu7}L^{4pPz~_Ag0jne_qf2g>Ky zODg13d|g5tY(N{pwe1&=Loc?r=SGJiU{MJ;$~!5dS!R-D7MtV(4zeYH%g`HtgezN2 zbn<$v09^%YidkscASoLZZ}Zh&1G)~h>D)u@Az}~7H}2a1j<$^`xHiFc^VCO+x=2x% zIJhaHEw%+T3^en^LbEL-TYfk2N@$pE1Kk5^iSJVDD6x);)AuD5Wf7qJK&|I`INRm? zA;H5Bj&?OW=mA$}0$;WAScq13l1h0$ha@!5x?t<ECHzbn6m{RfVztM32+ delta 3989 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 diff --git a/recruitment/__pycache__/views_frontend.cpython-312.pyc b/recruitment/__pycache__/views_frontend.cpython-312.pyc index 7c33a4c37f1a51effb9f4a8f0a9038edf0a447de..4c1b0db179447afc237198cbb5f59fe9e5b32619 100644 GIT binary patch delta 3702 zcmai04Nz3q72dncF0ja-fFg>l=mHBO2mw)0evHvbz#mWnf7X@vfaPzO`&LkDL}$>b zNej)HX-U-ziYS7q_Ek-%lTp;x)W#ScQ=P4~n#6J1W~$NF#?~Y~=PrU8GkpW!p8I|0 zoOkZI@4b7E-PE4=OzZy#e}AI}FQrsx&-$_d81~iC*eH+mFYr>A5sU!4?d$g@Rom*o8yvBEc>eY|$WFEG-f2Qo$}8WOXIG^iblJ*0SQMBCJ?c zm9(;z4K;4HmKwJTCuM!6aW=uPM&9;J6V?cR4f5rpZNm*M7m9?6ER?~9M&s&8LaIQi zQb@y%lh-qgT^+Bliyf`?EQ~-}J{|r)=+VHbya*qO)x>EuJP)?VS^`_I< z-v@_Mrs_{1Z%}WhBx_kG1gDKnP|14_g4@TgSQ~A%cF8@WyjHf=%aRq_YU57w*oy$A zX=cpUk~UL+5{Dt`K-vfvaDe241XIc9lzEi;W%@Ua^}@Z3GJ~06g}Z_6%~so{%gm65 z;|H1Gh?owknG?|Idc-v207Vl-!A~-?*Z}-IU<`bbxsM%!_N;U443x}V!1CbW%mNH} zICGitJOvPB!m?R&wC6Q&v8M^bveQMxXeiH43F;-EWdxxF7vN<4RO3Z53FgAh>}+-s zOga6S^FmIn@e)Y{3*nob1a?Ujk$*4-+S9#YV?row&dnT5eQUdI0nUl+a2{s!&E6G#5tqx#qy zE3G!CQ{i90Z;R_A{^_BEh<4fQWUI?zt+I2O{|gG1%wmt=ttBZTpL?8q$%&p&nSTwJ zmRMpQBG3O#@D;%$1h;-3MoUkz->RmiJG5A1Ur98(0Uwo|XLsP8(oM__5oOaN7_Jo8 z66gpVlqi&}FsLbuGmIj0G`v%m;}=C{IKdeByeyjO!F&0H*SwH$`_|eNm$gZjYq){N zBOOk;-s*HHc9-2zuUKuBmE4srINl)f0cYO+`^+^C6W%YfC! z8<(R8ZaErWs;1R0)v-PBmLr<|8h+uJf-lVtM>V?(uR5n2Ix)OC4sJOkV6XG-B;G~o zR$(wV5m*W6ZR7OuO$g0?bJp4FOhqo6Ypv4kD+Vv)Czh`xkM#u2 z1oU)Sj8ASj6Pe*V4p%;WM<)d}AYjn*w}l;RW6Fl}|A1UK5YW^6J+t!kgq)te#|vf! z#>Vk2&!rdP|M>p&(swiS`yTh_t4GgfUwXq#&-IDs(d;|lVXrs63-a)dc10 znZ{6m!)CH)LsV4^=vv1b#MAp1nADobTA;Gk)bfJmYz{D&CYtLK&63GHZ-%+R^Nk)} zO%5(8Zm()=)YGLv*4A;X0!p{Od}hbNFmWeZ5>xA2E|lR$QhEJebJR3==;@+4~4G(Xo*ClX)28V7&KnGO>~Z zNQpd3_Ae1E-r|tr_Ej8fdAV%GyB>#K5Mh3gJX8e58xxs4m*)WR8koxJl1j?hp9i|ilJ@Em~;sj*}YPtC`3{u0Y0@|-69xdO!$ec)2Z zME!Y`H>r<0+I8#{?AVoJxIq5haC%p@--l%Wo?rl`?Vc4OmQs)+vslVYBwvQM-4^{P zXsYV_yYK13CJ-xTu#sxaCO8YHyQZ`2>f^3O%=dT1oI`+yiDSUAFUD{e-vRys?A#Z{ zCcu~7A#gWhlJP3#A^0Oa-j}Jp%3wmy0@&7FC46FyH%KRV0MYvsaKkO!e;=jXo_WSw zBoRCU=$V1*^-)hd`xLAP<^_xDhEFk{F*p^U9C(`o2z+sqP^LmhPh#Mv1=qsHZZ72c zRPJpCdji5C|KMSRCj$%|h&A4!FoIx6>P;Rgh99E4rGh7uv_oy^O^}4e<5$+W!Fv CCh}7N delta 3272 zcmZ`*3s6+o8Qybo7xp58AU?sYsOX}GhX~61VGL>npOCDJ>$>-Xi|m7QS5az0oKdk& z8@0cgNsLudK~Vw8jT1W^lGYe)v}vr(q|G{7Yi#Q1)Uh_1HnFDt|FaION!(#R{_}nR z>pbqg=bg``lh>q}3o$XW0e^o@i*Oh3j2Xr5^-XG*V$@_MeY4>hDFy>vkfygvhGz}R z^vwn(Tb-azJ~k*M*%kfRt#)nF!P;yb3DU`GUrAjEXP{~7O>OiiAg3m|3 zKtxO#h*+c~7upspMYbhMv2CeRVq1m{mJT#%fZY*Ut;#dX95<^sD9;`stT@1wxqBjz zohR7&f_-j)T_D&>!B!2h3kADKu!|qEOO&O8T_)J-hiuIrqs@kGJpZtb2wJJiK{HzG z75irPaOn&-heQiki<4S`lL{}~O2IpjU-{S~I0f%SewA2+@Pe)qiVGD*DB%Tl2}MPv zPMr{syAWPEWdpOW)+ZP}lOQ%K5%Qn@hV6%(^rwsmu-#j_ExnsX2hA?0S8*%OfC_^% zQ`jL$&uqv)gbuuepp)P*f@VUnh##MaW-x9b(4nXSr@N7NBYjstmpM^l9q^CoxkIcG zyaYL3N6-PO*_p;ZG$VC;c80{d;b%Ex#d}a3J+@L9X0SP6QT+ zD=c9BP&#E4)D<3N2S6@5$KHcWMOCaEQi>N~fWNp#K1%@v1@MRBXV_U0kWum)2AnOK z8`n;94Z$#i58Gx~=CKOkD*9`5>kVi@b7#sMwC`I3=x(jjKG7%P~4Ts$x8A?1Oq(TPY z9vA_BkIa_9k34B|4f-sS(0Q5O>#2@lC&AQ|V$w0ZB^9@AFg*ItH#M-^@P5-Q zd`uoR^<@Q39yPGqr*Ip_^CW`h1gEGjy^wh=4ObA{rr}Ds!slV4$y&Ox4q2~m*Y-1Z zPk#`oU~_28t!7?N?yCvh1g}t_C?Ik-5VwY)5dqzs$mRqZK>%J^mtdG=W4@Q3HK#t3em%KvAfRLVgziIgC#T12{80Csn#Z<=yBFz2_^l_W zU-|va{7%UIyWOK_i#!O6FS$I;ms{&Y$IIVVu^la>%hEq?A-9Sg_rs9#R#&A;oal|3*RD5wQwI5 z^b_0x|JG@&7xr$=i5p795 zn@oqutrt%3n9Mri_KrCT`;Z7415Ry?#`lxBioxuiGZI~7lNe&f>*^4^x-;F_hvY2% z^v)kkmXp{7zIG+I!hcDo#0&6W(8N`3ZDRoItl2eo_+d(NgdiWqO5R20QP{U@Y@|*m z7Cf^n%icqFFM>5vOb&P3x0bt92PP-KpeZ!QchCl}%b}@!o!h1IlN5FWL5sxfp)apR ze0upQk_a67;@2u8*cE7hU9X@3EJ2O(PVx{LXkCJl_&(zSRUyOY_v720X2p9ET81pe zC9-&Z0ZSde{1ko|_PjBzm8NBl3C1c7PH(->(e$*(r>Kn@f0q)!NWoO}c;gjtKmGv%OiS^W@(Sv-UPb ze@Nz8f_I^3Z_#M6tAZ4n#jgII+eaI+Cf-erG#@$7kpCN8Of^{%tmL*Eb3@#Sr$e&PR zfZcA@!1hiv`v&AAd2pt4 zG-Q-Gp#R87_80i{NX1amZ}{SIje(^!BlbFFB#`mtuVq5)k!kQ|SB!K$l)Aqx6>fI5 zVS4}3sqzhSCWwd5qZ#pH_*?2<E-<*GW diff --git a/recruitment/models.py b/recruitment/models.py index 5e80ec2..4ed1316 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -679,6 +679,32 @@ class Candidate(Base): return future_meetings or today_future_meetings + @property + def check_and_retry_ai_scoring(self): + """ + Triggers an immediate save ONLY if: + 1. The resume hasn't been parsed yet. + 2. At least 5 minutes have passed since the last attempt. + Returns True if a save was performed, False otherwise. + """ + from datetime import timedelta + + min_delay = timedelta(minutes=5) + + time_since_last_attempt = timezone.now() - self.created_at + + if not self.is_resume_parsed and time_since_last_attempt >= min_delay: + + # 1. Update the retry timestamp + self.last_retry_attempt = timezone.now() + + + self.save() + + return True + + return False + # @property # def time_to_hire(self): # time_to_hire=self.hired_date-self.created_at diff --git a/recruitment/urls.py b/recruitment/urls.py index 49fb42a..9d61e6f 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -34,6 +34,7 @@ urlpatterns = [ path('candidate//view/', views_frontend.candidate_detail, name='candidate_detail'), path('candidate//resume-template/', views_frontend.candidate_resume_template_view, name='candidate_resume_template'), path('candidate//update-stage/', views_frontend.candidate_update_stage, name='candidate_update_stage'), + path('candidate//retry-scoring/', views_frontend.retry_scoring_view, name='candidate_retry_scoring'), # Training URLs path('training/', views_frontend.TrainingListView.as_view(), name='training_list'), @@ -201,23 +202,23 @@ urlpatterns = [ # API URLs for candidate management path('api/candidate//', views.api_candidate_detail, name='api_candidate_detail'), - # Admin Notification API - path('api/admin/notification-count/', views.api_notification_count, name='admin_notification_count'), + # # Admin Notification API + # path('api/admin/notification-count/', views.api_notification_count, name='admin_notification_count'), - # Agency Notification API - path('api/agency/notification-count/', views.api_notification_count, name='api_agency_notification_count'), + # # Agency Notification API + # path('api/agency/notification-count/', views.api_notification_count, name='api_agency_notification_count'), - # SSE Notification Stream - path('api/notifications/stream/', views.notification_stream, name='notification_stream'), + # # SSE Notification Stream + # path('api/notifications/stream/', views.notification_stream, name='notification_stream'), - # Notification URLs - path('notifications/', views.notification_list, name='notification_list'), - path('notifications//', views.notification_detail, name='notification_detail'), - path('notifications//mark-read/', views.notification_mark_read, name='notification_mark_read'), - path('notifications//mark-unread/', views.notification_mark_unread, name='notification_mark_unread'), - path('notifications//delete/', views.notification_delete, name='notification_delete'), - path('notifications/mark-all-read/', views.notification_mark_all_read, name='notification_mark_all_read'), - path('api/notification-count/', views.api_notification_count, name='api_notification_count'), + # # Notification URLs + # path('notifications/', views.notification_list, name='notification_list'), + # path('notifications//', views.notification_detail, name='notification_detail'), + # path('notifications//mark-read/', views.notification_mark_read, name='notification_mark_read'), + # path('notifications//mark-unread/', views.notification_mark_unread, name='notification_mark_unread'), + # path('notifications//delete/', views.notification_delete, name='notification_delete'), + # path('notifications/mark-all-read/', views.notification_mark_all_read, name='notification_mark_all_read'), + # path('api/notification-count/', views.api_notification_count, name='api_notification_count'), #participants urls diff --git a/recruitment/views.py b/recruitment/views.py index b2d40cf..eb4be46 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -2582,314 +2582,314 @@ def agency_delete(request, slug): # Notification Views -@login_required -def notification_list(request): - """List all notifications for the current user""" - # Get filter parameters - status_filter = request.GET.get('status', '') - type_filter = request.GET.get('type', '') +# @login_required +# def notification_list(request): +# """List all notifications for the current user""" +# # Get filter parameters +# status_filter = request.GET.get('status', '') +# type_filter = request.GET.get('type', '') - # Base queryset - notifications = Notification.objects.filter(recipient=request.user).order_by('-created_at') +# # Base queryset +# notifications = Notification.objects.filter(recipient=request.user).order_by('-created_at') - # Apply filters - if status_filter: - if status_filter == 'unread': - notifications = notifications.filter(status=Notification.Status.PENDING) - elif status_filter == 'read': - notifications = notifications.filter(status=Notification.Status.READ) - elif status_filter == 'sent': - notifications = notifications.filter(status=Notification.Status.SENT) +# # Apply filters +# if status_filter: +# if status_filter == 'unread': +# notifications = notifications.filter(status=Notification.Status.PENDING) +# elif status_filter == 'read': +# notifications = notifications.filter(status=Notification.Status.READ) +# elif status_filter == 'sent': +# notifications = notifications.filter(status=Notification.Status.SENT) - if type_filter: - if type_filter == 'in_app': - notifications = notifications.filter(notification_type=Notification.NotificationType.IN_APP) - elif type_filter == 'email': - notifications = notifications.filter(notification_type=Notification.NotificationType.EMAIL) +# if type_filter: +# if type_filter == 'in_app': +# notifications = notifications.filter(notification_type=Notification.NotificationType.IN_APP) +# elif type_filter == 'email': +# notifications = notifications.filter(notification_type=Notification.NotificationType.EMAIL) - # Pagination - paginator = Paginator(notifications, 20) # Show 20 notifications per page - page_number = request.GET.get('page') - page_obj = paginator.get_page(page_number) +# # Pagination +# paginator = Paginator(notifications, 20) # Show 20 notifications per page +# page_number = request.GET.get('page') +# page_obj = paginator.get_page(page_number) - # Statistics - total_notifications = notifications.count() - unread_notifications = notifications.filter(status=Notification.Status.PENDING).count() - email_notifications = notifications.filter(notification_type=Notification.NotificationType.EMAIL).count() +# # Statistics +# total_notifications = notifications.count() +# unread_notifications = notifications.filter(status=Notification.Status.PENDING).count() +# email_notifications = notifications.filter(notification_type=Notification.NotificationType.EMAIL).count() - context = { - 'page_obj': page_obj, - 'total_notifications': total_notifications, - 'unread_notifications': unread_notifications, - 'email_notifications': email_notifications, - 'status_filter': status_filter, - 'type_filter': type_filter, - } - return render(request, 'recruitment/notification_list.html', context) +# context = { +# 'page_obj': page_obj, +# 'total_notifications': total_notifications, +# 'unread_notifications': unread_notifications, +# 'email_notifications': email_notifications, +# 'status_filter': status_filter, +# 'type_filter': type_filter, +# } +# return render(request, 'recruitment/notification_list.html', context) -@login_required -def notification_detail(request, notification_id): - """View details of a specific notification""" - notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) +# @login_required +# def notification_detail(request, notification_id): +# """View details of a specific notification""" +# notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) - # Mark as read if it was pending - if notification.status == Notification.Status.PENDING: - notification.status = Notification.Status.READ - notification.save(update_fields=['status']) +# # Mark as read if it was pending +# if notification.status == Notification.Status.PENDING: +# notification.status = Notification.Status.READ +# notification.save(update_fields=['status']) - context = { - 'notification': notification, - } - return render(request, 'recruitment/notification_detail.html', context) +# context = { +# 'notification': notification, +# } +# return render(request, 'recruitment/notification_detail.html', context) -@login_required -def notification_mark_read(request, notification_id): - """Mark a notification as read""" - notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) +# @login_required +# def notification_mark_read(request, notification_id): +# """Mark a notification as read""" +# notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) - if notification.status == Notification.Status.PENDING: - notification.status = Notification.Status.READ - notification.save(update_fields=['status']) +# if notification.status == Notification.Status.PENDING: +# notification.status = Notification.Status.READ +# notification.save(update_fields=['status']) - if 'HX-Request' in request.headers: - return HttpResponse(status=200) # HTMX success response +# if 'HX-Request' in request.headers: +# return HttpResponse(status=200) # HTMX success response - return redirect('notification_list') +# return redirect('notification_list') -@login_required -def notification_mark_unread(request, notification_id): - """Mark a notification as unread""" - notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) +# @login_required +# def notification_mark_unread(request, notification_id): +# """Mark a notification as unread""" +# notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) - if notification.status == Notification.Status.READ: - notification.status = Notification.Status.PENDING - notification.save(update_fields=['status']) +# if notification.status == Notification.Status.READ: +# notification.status = Notification.Status.PENDING +# notification.save(update_fields=['status']) - if 'HX-Request' in request.headers: - return HttpResponse(status=200) # HTMX success response +# if 'HX-Request' in request.headers: +# return HttpResponse(status=200) # HTMX success response - return redirect('notification_list') +# return redirect('notification_list') -@login_required -def notification_delete(request, notification_id): - """Delete a notification""" - notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) +# @login_required +# def notification_delete(request, notification_id): +# """Delete a notification""" +# notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) - if request.method == 'POST': - notification.delete() - messages.success(request, 'Notification deleted successfully!') - return redirect('notification_list') +# if request.method == 'POST': +# notification.delete() +# messages.success(request, 'Notification deleted successfully!') +# return redirect('notification_list') - # For GET requests, show confirmation page - context = { - 'notification': notification, - 'title': 'Delete Notification', - 'message': f'Are you sure you want to delete this notification?', - 'cancel_url': reverse('notification_detail', kwargs={'notification_id': notification.id}), - } - return render(request, 'recruitment/notification_confirm_delete.html', context) +# # For GET requests, show confirmation page +# context = { +# 'notification': notification, +# 'title': 'Delete Notification', +# 'message': f'Are you sure you want to delete this notification?', +# 'cancel_url': reverse('notification_detail', kwargs={'notification_id': notification.id}), +# } +# return render(request, 'recruitment/notification_confirm_delete.html', context) -@login_required -def notification_mark_all_read(request): - """Mark all notifications as read for the current user""" - if request.method == 'POST': - Notification.objects.filter( - recipient=request.user, - status=Notification.Status.PENDING - ).update(status=Notification.Status.READ) +# @login_required +# def notification_mark_all_read(request): +# """Mark all notifications as read for the current user""" +# if request.method == 'POST': +# Notification.objects.filter( +# recipient=request.user, +# status=Notification.Status.PENDING +# ).update(status=Notification.Status.READ) - messages.success(request, 'All notifications marked as read!') - return redirect('notification_list') +# messages.success(request, 'All notifications marked as read!') +# return redirect('notification_list') - # For GET requests, show confirmation page - unread_count = Notification.objects.filter( - recipient=request.user, - status=Notification.Status.PENDING - ).count() +# # For GET requests, show confirmation page +# unread_count = Notification.objects.filter( +# recipient=request.user, +# status=Notification.Status.PENDING +# ).count() - context = { - 'unread_count': unread_count, - 'title': 'Mark All as Read', - 'message': f'Are you sure you want to mark all {unread_count} notifications as read?', - 'cancel_url': reverse('notification_list'), - } - return render(request, 'recruitment/notification_confirm_all_read.html', context) +# context = { +# 'unread_count': unread_count, +# 'title': 'Mark All as Read', +# 'message': f'Are you sure you want to mark all {unread_count} notifications as read?', +# 'cancel_url': reverse('notification_list'), +# } +# return render(request, 'recruitment/notification_confirm_all_read.html', context) -@login_required -def api_notification_count(request): - """API endpoint to get unread notification count and recent notifications""" - # Get unread notifications - unread_notifications = Notification.objects.filter( - recipient=request.user, - status=Notification.Status.PENDING - ).order_by('-created_at') +# @login_required +# def api_notification_count(request): +# """API endpoint to get unread notification count and recent notifications""" +# # Get unread notifications +# unread_notifications = Notification.objects.filter( +# recipient=request.user, +# status=Notification.Status.PENDING +# ).order_by('-created_at') - # Get recent notifications (last 5) - recent_notifications = Notification.objects.filter( - recipient=request.user - ).order_by('-created_at')[:5] +# # Get recent notifications (last 5) +# recent_notifications = Notification.objects.filter( +# recipient=request.user +# ).order_by('-created_at')[:5] - # Prepare recent notifications data - recent_data = [] - for notification in recent_notifications: - time_ago = '' - if notification.created_at: - from datetime import datetime, timezone - now = timezone.now() - diff = now - notification.created_at +# # Prepare recent notifications data +# recent_data = [] +# for notification in recent_notifications: +# time_ago = '' +# if notification.created_at: +# from datetime import datetime, timezone +# now = timezone.now() +# diff = now - notification.created_at - if diff.days > 0: - time_ago = f'{diff.days}d ago' - elif diff.seconds > 3600: - hours = diff.seconds // 3600 - time_ago = f'{hours}h ago' - elif diff.seconds > 60: - minutes = diff.seconds // 60 - time_ago = f'{minutes}m ago' - else: - time_ago = 'Just now' +# if diff.days > 0: +# time_ago = f'{diff.days}d ago' +# elif diff.seconds > 3600: +# hours = diff.seconds // 3600 +# time_ago = f'{hours}h ago' +# elif diff.seconds > 60: +# minutes = diff.seconds // 60 +# time_ago = f'{minutes}m ago' +# else: +# time_ago = 'Just now' - recent_data.append({ - 'id': notification.id, - 'message': notification.message[:100] + ('...' if len(notification.message) > 100 else ''), - 'type': notification.get_notification_type_display(), - 'status': notification.get_status_display(), - 'time_ago': time_ago, - 'url': reverse('notification_detail', kwargs={'notification_id': notification.id}) - }) +# recent_data.append({ +# 'id': notification.id, +# 'message': notification.message[:100] + ('...' if len(notification.message) > 100 else ''), +# 'type': notification.get_notification_type_display(), +# 'status': notification.get_status_display(), +# 'time_ago': time_ago, +# 'url': reverse('notification_detail', kwargs={'notification_id': notification.id}) +# }) - return JsonResponse({ - 'count': unread_notifications.count(), - 'recent_notifications': recent_data - }) +# return JsonResponse({ +# 'count': unread_notifications.count(), +# 'recent_notifications': recent_data +# }) -@login_required -def notification_stream(request): - """SSE endpoint for real-time notifications""" - from django.http import StreamingHttpResponse - import json - import time - from .signals import SSE_NOTIFICATION_CACHE +# @login_required +# def notification_stream(request): +# """SSE endpoint for real-time notifications""" +# from django.http import StreamingHttpResponse +# import json +# import time +# from .signals import SSE_NOTIFICATION_CACHE - def event_stream(): - """Generator function for SSE events""" - user_id = request.user.id - last_notification_id = 0 +# def event_stream(): +# """Generator function for SSE events""" +# user_id = request.user.id +# last_notification_id = 0 - # Get initial last notification ID - last_notification = Notification.objects.filter( - recipient=request.user - ).order_by('-id').first() - if last_notification: - last_notification_id = last_notification.id +# # Get initial last notification ID +# last_notification = Notification.objects.filter( +# recipient=request.user +# ).order_by('-id').first() +# if last_notification: +# last_notification_id = last_notification.id - # Send any cached notifications first - cached_notifications = SSE_NOTIFICATION_CACHE.get(user_id, []) - for cached_notification in cached_notifications: - if cached_notification['id'] > last_notification_id: - yield f"event: new_notification\n" - yield f"data: {json.dumps(cached_notification)}\n\n" - last_notification_id = cached_notification['id'] +# # Send any cached notifications first +# cached_notifications = SSE_NOTIFICATION_CACHE.get(user_id, []) +# for cached_notification in cached_notifications: +# if cached_notification['id'] > last_notification_id: +# yield f"event: new_notification\n" +# yield f"data: {json.dumps(cached_notification)}\n\n" +# last_notification_id = cached_notification['id'] - while True: - try: - # Check for new notifications from cache first - cached_notifications = SSE_NOTIFICATION_CACHE.get(user_id, []) - new_cached = [n for n in cached_notifications if n['id'] > last_notification_id] +# while True: +# try: +# # Check for new notifications from cache first +# cached_notifications = SSE_NOTIFICATION_CACHE.get(user_id, []) +# new_cached = [n for n in cached_notifications if n['id'] > last_notification_id] - for notification_data in new_cached: - yield f"event: new_notification\n" - yield f"data: {json.dumps(notification_data)}\n\n" - last_notification_id = notification_data['id'] +# for notification_data in new_cached: +# yield f"event: new_notification\n" +# yield f"data: {json.dumps(notification_data)}\n\n" +# last_notification_id = notification_data['id'] - # Also check database for any missed notifications - new_notifications = Notification.objects.filter( - recipient=request.user, - id__gt=last_notification_id - ).order_by('id') +# # Also check database for any missed notifications +# new_notifications = Notification.objects.filter( +# recipient=request.user, +# id__gt=last_notification_id +# ).order_by('id') - if new_notifications.exists(): - for notification in new_notifications: - # Prepare notification data - time_ago = '' - if notification.created_at: - now = timezone.now() - diff = now - notification.created_at +# if new_notifications.exists(): +# for notification in new_notifications: +# # Prepare notification data +# time_ago = '' +# if notification.created_at: +# now = timezone.now() +# diff = now - notification.created_at - if diff.days > 0: - time_ago = f'{diff.days}d ago' - elif diff.seconds > 3600: - hours = diff.seconds // 3600 - time_ago = f'{hours}h ago' - elif diff.seconds > 60: - minutes = diff.seconds // 60 - time_ago = f'{minutes}m ago' - else: - time_ago = 'Just now' +# if diff.days > 0: +# time_ago = f'{diff.days}d ago' +# elif diff.seconds > 3600: +# hours = diff.seconds // 3600 +# time_ago = f'{hours}h ago' +# elif diff.seconds > 60: +# minutes = diff.seconds // 60 +# time_ago = f'{minutes}m ago' +# else: +# time_ago = 'Just now' - notification_data = { - 'id': notification.id, - 'message': notification.message[:100] + ('...' if len(notification.message) > 100 else ''), - 'type': notification.get_notification_type_display(), - 'status': notification.get_status_display(), - 'time_ago': time_ago, - 'url': reverse('notification_detail', kwargs={'notification_id': notification.id}) - } +# notification_data = { +# 'id': notification.id, +# 'message': notification.message[:100] + ('...' if len(notification.message) > 100 else ''), +# 'type': notification.get_notification_type_display(), +# 'status': notification.get_status_display(), +# 'time_ago': time_ago, +# 'url': reverse('notification_detail', kwargs={'notification_id': notification.id}) +# } - # Send SSE event - yield f"event: new_notification\n" - yield f"data: {json.dumps(notification_data)}\n\n" +# # Send SSE event +# yield f"event: new_notification\n" +# yield f"data: {json.dumps(notification_data)}\n\n" - last_notification_id = notification.id +# last_notification_id = notification.id - # Update count after sending new notifications - unread_count = Notification.objects.filter( - recipient=request.user, - status=Notification.Status.PENDING - ).count() +# # Update count after sending new notifications +# unread_count = Notification.objects.filter( +# recipient=request.user, +# status=Notification.Status.PENDING +# ).count() - count_data = {'count': unread_count} - yield f"event: count_update\n" - yield f"data: {json.dumps(count_data)}\n\n" +# count_data = {'count': unread_count} +# yield f"event: count_update\n" +# yield f"data: {json.dumps(count_data)}\n\n" - # Send heartbeat every 30 seconds - yield f"event: heartbeat\n" - yield f"data: {json.dumps({'timestamp': int(time.time())})}\n\n" +# # Send heartbeat every 30 seconds +# yield f"event: heartbeat\n" +# yield f"data: {json.dumps({'timestamp': int(time.time())})}\n\n" - # Wait before next check - time.sleep(5) # Check every 5 seconds +# # Wait before next check +# time.sleep(5) # Check every 5 seconds - except Exception as e: - # Send error event and continue - error_data = {'error': str(e)} - yield f"event: error\n" - yield f"data: {json.dumps(error_data)}\n\n" - time.sleep(10) # Wait longer on error +# except Exception as e: +# # Send error event and continue +# error_data = {'error': str(e)} +# yield f"event: error\n" +# yield f"data: {json.dumps(error_data)}\n\n" +# time.sleep(10) # Wait longer on error - response = StreamingHttpResponse( - event_stream(), - content_type='text/event-stream' - ) +# response = StreamingHttpResponse( +# event_stream(), +# content_type='text/event-stream' +# ) - # Set SSE headers - response['Cache-Control'] = 'no-cache' - response['X-Accel-Buffering'] = 'no' # Disable buffering for nginx - response['Connection'] = 'keep-alive' +# # Set SSE headers +# response['Cache-Control'] = 'no-cache' +# response['X-Accel-Buffering'] = 'no' # Disable buffering for nginx +# response['Connection'] = 'keep-alive' - context = { - 'agency': agency, - 'page_obj': page_obj, - 'stage_filter': stage_filter, - 'total_candidates': candidates.count(), - } - return render(request, 'recruitment/agency_candidates.html', context) + # context = { + # 'agency': agency, + # 'page_obj': page_obj, + # 'stage_filter': stage_filter, + # 'total_candidates': candidates.count(), + # } + # return render(request, 'recruitment/agency_candidates.html', context) @login_required @@ -2976,7 +2976,7 @@ def agency_assignment_create(request,slug=None): try: from django.forms import HiddenInput form.initial['agency'] = agency - form.fields['agency'].widget = HiddenInput() + # form.fields['agency'].widget = HiddenInput() except HiringAgency.DoesNotExist: pass diff --git a/recruitment/views_frontend.py b/recruitment/views_frontend.py index 9b07a52..b403475 100644 --- a/recruitment/views_frontend.py +++ b/recruitment/views_frontend.py @@ -222,12 +222,11 @@ class CandidateDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): slug_url_kwarg = 'slug' -# def job_detail(request, slug): -# job = get_object_or_404(models.JobPosting, slug=slug, status='Published') -# form = forms.CandidateForm() - -# return render(request, 'jobs/job_detail.html', {'job': job, 'form': form}) - +def retry_scoring_view(request,slug): + if request.method == 'POST': + candidate = get_object_or_404(models.Candidate, slug=slug) + candidate.save() + return redirect('candidate_detail', slug=candidate.slug) diff --git a/templates/agency_base.html b/templates/agency_base.html index e5f2451..03b71e3 100644 --- a/templates/agency_base.html +++ b/templates/agency_base.html @@ -9,7 +9,7 @@ {% block title %}{% trans 'KAAUH Agency Portal' %}{% endblock %} - {% comment %} Load correct Bootstrap CSS file for RTL/LTR {% endcomment %} + {# Load correct Bootstrap CSS file for RTL/LTR #} {% if LANGUAGE_CODE == 'ar' %} {% else %} @@ -24,91 +24,94 @@ - {% comment %}
+
-
-
-
-
-
-
+
+
{% trans 'Saudi Vision 2030' %} - -
-
-
جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية
-
ومستشفى الملك عبدالله بن عبدالرحمن التخصصي
-
Princess Nourah bint Abdulrahman University
-
King Abdullah bin Abdulaziz University Hospital
+
+
+
+
+ {% if LANGUAGE_CODE == 'ar' %} + جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية +
+ ومستشفى الملك عبدالله بن عبدالعزيز التخصصي + {% else %} + Princess Nourah bint Abdulrahman University +
+ King Abdullah bin Abdulaziz University Hospital + {% endif %}
- KAAUH Logo + KAAUH Logo
-
{% endcomment %} +
+ + {# Using inline style for nav background color - replace with a dedicated CSS class (e.g., .bg-kaauh-nav) if defined in main.css #} +
+ +
+ {# Messages Block (Correct) #} {% if messages %} {% for message in messages %}
+ {# Footer (Correct) #}