From 5073ef280ca46b4ca02106b49ed218ff8723783f Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 24 Jan 2026 06:32:35 +0000 Subject: [PATCH] Autosave: 20260124-063234 --- config/__pycache__/__init__.cpython-311.pyc | Bin 159 -> 159 bytes config/__pycache__/settings.cpython-311.pyc | Bin 5552 -> 5552 bytes config/__pycache__/urls.cpython-311.pyc | Bin 1557 -> 1557 bytes config/__pycache__/wsgi.cpython-311.pyc | Bin 679 -> 679 bytes core/__pycache__/__init__.cpython-311.pyc | Bin 157 -> 157 bytes core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 4933 bytes core/__pycache__/apps.cpython-311.pyc | Bin 524 -> 524 bytes .../context_processors.cpython-311.pyc | Bin 763 -> 763 bytes core/__pycache__/forms.cpython-311.pyc | Bin 0 -> 7980 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 12274 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 2307 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 17942 bytes core/admin.py | 68 +- core/forms.py | 82 ++ core/migrations/0001_initial.py | 78 ++ ...eventparticipation_interaction_and_more.py | 75 ++ core/migrations/0003_tenantuserrole.py | 28 + .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 4231 bytes ...ation_interaction_and_more.cpython-311.pyc | Bin 0 -> 4600 bytes .../0003_tenantuserrole.cpython-311.pyc | Bin 0 -> 1869 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 168 -> 168 bytes core/models.py | 160 +++- core/templates/base.html | 80 +- core/templates/core/index.html | 203 ++--- core/templates/core/voter_detail.html | 782 ++++++++++++++++++ core/templates/core/voter_list.html | 79 ++ core/urls.py | 25 +- core/views.py | 287 ++++++- db/config.php | 6 +- static/css/custom.css | 111 ++- staticfiles/css/custom.css | 118 ++- 31 files changed, 1974 insertions(+), 208 deletions(-) create mode 100644 core/__pycache__/forms.cpython-311.pyc create mode 100644 core/forms.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/0002_donation_event_eventparticipation_interaction_and_more.py create mode 100644 core/migrations/0003_tenantuserrole.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0002_donation_event_eventparticipation_interaction_and_more.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0003_tenantuserrole.cpython-311.pyc create mode 100644 core/templates/core/voter_detail.html create mode 100644 core/templates/core/voter_list.html diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 423a6362b2322713e75da67a35e209e76169dbae..b2a6eeec5462f7964826b1ffa88c2069394498dd 100644 GIT binary patch delta 19 ZcmbQwIG>SwIWI340}y-&DxSza1pqB>1pEL1 delta 19 ZcmbQwIG>SwIWI340}xbw%b&y+NCMIWI340}y-&D&EMwR1^R^j|I2@ delta 20 acmdm>y+NCMIWI340}xbw%iqYoR1^R_83p11 diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94ece283a83ff1af1d71f1b265c943eb37a..c5a37afe9ed655e4911ba2fc6cca47af8c425059 100644 GIT binary patch delta 20 acmbQrGnI#XIWI340}y-&D&EM=#RdQ|>;#$s delta 20 acmbQrGnI#XIWI340}xbw%iqY&#RdQ}b_B!# diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index 9c49e09df194d2dbcad4868349c9177db4b15571..945c6e2ad9ba07e6b52b7c06823fad37038eae50 100644 GIT binary patch delta 20 acmZ3^x}24JIWI340}y-&D&ELFhY0{Qyahf0 delta 20 acmZ3^x}24JIWI340}xbw%iqX7hY0{RMg?d9 diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc index 74b111269bd81aac528770a53e2f849291524d56..c3e6f005afa3b16eb563fa39643fd65d8a2f6ca4 100644 GIT binary patch delta 19 ZcmbQsIG2%oIWI340}y-&DxSza2>>l51oi*` delta 19 ZcmbQsIG2%oIWI340}xbw%b&>mG1t0(b diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392d6714413db63120e4233d2e96cbadb5de..9eeed8d35f6e7b259bfb8c929633f848cd2b4b39 100644 GIT binary patch literal 4933 zcmd5U z1jipgdCs3vN%|Kb8b{>t=3rBiq!*Gc*`z4B#2`enWm_rAu3{*zYN)PeXl}%axIIRX z8#SVCuhHx3hVI6Um>W0ZZo){oNh3)lS-K(F>T}7~D0$r1_|Qwhe+57PF#2T)W(OM2 zRC!5Y9)A8IFd~4_16qTP(RPfNF#M`9B^jvzqS^s53W&X3Bc=n0S_ecO5Mx~wq``h?89-X6@`fDL3^7^E^km zX4$S&dV}jE7w3`tv}Bb$p7gH|DwN$V7b)+%S@I~e3Z7Fg-Ctj$JbA5LvhZn%daGrd z#}|wAXjc2N4C*`%7pJsxmln#*=Gx*@3ebb%@`J@Zr{Js!3?6Nu@mR};c%Oi|<2<27 zXSH0m3(d6&H;t8jFa!@TBq|ximJFFHhH4W-vt_VS;puimZkCEpi82*lSlxQzk`}o}pLxvMk`7=@dN-e6ExR;deON7d)jWMh z3stYo^1qkalggS^pm{Kx=7oP3*47!OkPtm^QyBqKYo5FDvD)HpJW*T3lZ`f1J6k*f z(?=GAwTdkpD%Dnq5wR7pTjep|?yDGFaD$4*aJ5pbu5i7;sO3@Hw7eW)lYq6H>tQMU zWEw>V1dkV;if7tRWvytf^F)POtgvc6a%j=62qSopBYat5Xo=5w4DXPf5HYx^OoKQw z^L6Uu+MV6>WNm4;@9cJRwsr$g|7Coyv$+TxHrG}l$SN4C@l<<|n?_e5eR;(5Q(9$^ z$T^w42Wael5ILDAQNuJ*3J1k~T|X-zK>}W3)9@OP3D-Pwijd-L2GIf@n8h&^*o-RZ z;8qXJg9ip@YqxjP7i&v+`sSm4XYJTZL)xAU1Lg_g)PCWtCVj3LS;0tZ}?cpb*7NlmuL4JaON8 zSS?!Ya6JyCxyl4XZ7)geVllEG#i!V{#xdF zb|bPwhU;V)Etx}0f?{K?K6qvyC*GpiS0s6)sjoLsk~?IoPNop$5~2jf=3IU39HLx8 zluL**-PCWQ^z9HBo!*mWl0{2`0xkIvEy>NhQFatQ+n&_gb*C_Wd|Z=%F^ z$Yh;N9?wOqk9^S1CEL`GhNB=Nvze)no)3lM80C!z^^EvJTilLO-UK?rHC-t$)+h2I z8jVk9|lz$ k(b(=kvqR3-$yp4?Ji-LUCaq^hlFuW|JhGV$>x(_>e{8&SrZc24q%h_%0#ks&dbZi00bX`iZ^mIG64WDU<7Lb delta 20 acmeBS>0#ks&dbZi00dRv@;7obG64WD>I9Vl diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 75bf2234fb21a6b62efc5cec11af9512dd0c9616..3df58b8d927abf8cd8b21bb76af2aee146db87dc 100644 GIT binary patch delta 20 acmey(`kR$|IWI340}y-&D&ENbfe8RX+y*)T delta 20 acmey(`kR$|IWI340}xbw%iqZTfe8RYW(H&c diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb593c85b97dbe607582eb808454149d50eefabe GIT binary patch literal 7980 zcmeI1PfQe98o;Z%yQ*J91A?%NqqBhju!R)c|+b936YpDXE@pOkdp<4AM-nzKXr55 zbGYmyRKc`F(DH7eRmrpnX!$nKs$yDYpjEzs))uDa2d#sAf>J0GUH8c~xh>xkWHw)CvtEFFTtAK^VIT!@#skc+jC zhxWOix}dM&vX78^k{jV=_d^o$OmcVlkN_?8SUy(W)6l+x>~luY3s0|QuDC;|OchPDJP}pDYYCbj))kA~Qs?94+^ZwuxU6DO(o)G}g6fv< zZkWoFrjEvG12oC<==c&nsPha=BdqIG8v?T+5^IPgS!I$GOUS9H0%5--eU}PHv5AsI zv0E(x5|yYWj;Kmh)+mk!%hwCp^~aMb-SUpB@~EO~&$uP*!Ifa!%|uLTOMRP)>!~*7 zjuJ`f3EDQEptrPSIHI&g5>#np7J49g*YaTB(rS1T3)4=6c#!+eZ>gIU7F-NPbNNi^($V0j>q5z7;%qo8tYoc#;TWG$a0vGOHJ!+vjs;{k1h5bXwWuV!tW zJ0+qyYJUgup9kDLssEGkz4wvVB=wJX8zeBZ7leJz*KM2reYUPSQ`elX3z&6*1&;T9 z#oKc7)V#m?(V*%7P1e6BUR`u;B52X7Oo=qXb!?L_7L@b!#-aL`qgrZ7MB_B(ay)b=WI%U;u*zjci%>Pm zW^Ex36NR>xN_V4*hbG3g*T|-Ti#96#rGbYwFbW@K4>d-NybSM(xpb z?NPJ#=zQI-sY}y0AiCqH7Nf2^UDs{abuS3+tzcCk*0XyE<}6WH;^DZS!#LV14rH{G zr6knjntlK-~EEG=_t4OD3p#rx3 zeSx9hqUZz>D5qT*Mzm6_yX8%MI|g(IERU#BaBpm!lkZXry{joYJ&2m<7BZyM!w>>~ z9l?k}6h~1ULvb7h!xeTga=ZZK_3OuwgL9l$v^fyJg!A%hME5oRRm%~x<#*YZ{!B~1 zap8*5(w}a*YPMXR?0t0hv1s}mO#i-3VgJ-=lWaH1PM83)q%A|*W`8{wG}`;pq~9d{ zhI86u@B}vhD{KpnfK^(b#7GqNm~an70j))(b+K5)xaGBkE!^Jys5bz}et&6K6Dt#e zD|a}fX>;)Hca|e>W_K?XU7Fn`76IUBt*19z1mJo@T&d14&4#ku=CK&FmqTJWmH=1R z<}JhZZYUl+?Df#a;a>{6Z$|AUcme^fq0Q%`wjNO%G}sBK_18=dmx66cZ+M6e-A^!u zE-gAfDoig!b?62#Mf}qt6vHUk%zYk1B>+kv6-(>ptZw?~+O!B!&cAI2>mULuD$LztmbH*0~X#x)M zS;ILuK;+lhNhr|cDMqA#h%6S17`MEZP`Qf8L59fpm3B3;G9M6;TiJ?cZBM6&l+N;X z=`*NEAt`a`IqHJ#5;ySAvr=CoD~3UC;sBWJW@n`oTI-?u0?B`F8VTqMK+e7#uj$yv5D~oH+@TNGN zUi(ec>+sUoF_PYZxB{=U9F_imQ0^#Y`VKFd7y3^q=1-RCNg*3tX{S#JJ_P1j4B570v@y z-(T9*#L9d?s{X2Yi@OM{=$PZNgx>giu#U1_Xuzs_WqawoY_bXpZmE}`D17?(C5mGE z`{2?(1CdYBok-DbQ`_DJp`X^)_=(^nt*vEz*&Dcn_>F24DA(`?OLRboOK|NUAl?pi z=lNHy9WTyhTYECCJwJ;^Yfrj$*lZnUnDd(cM$;eI9L$|D$qtk3GD#py+B2kmwt4Qj z(b1nK111?ToO1(!g|P!s;3ioqfNe*BtrrNe?Fg{d1RG$YaF)4+?L_e z)$2C*R>(2?BhZ&rg&YuM8h#dAaPvGr&(#}+=Yosl2VD!G_z|V2hQ~eT(sD|eu3BVi z%lR7ns(9SwC@rUy>BJ&STh7sY&W#K z&8;$EhphTxm3Sp4auilevV&x;5HT4ivXoIoRy>T97apskQeu@9DN>$zGci&kUXpWe zRdsh&Hw{^KwOe+bzCWjK-Fxdh=bm%!{hLT6#K3h#kPB^{4D+9uu`aKf*!(XS!~C2P z7{OI!W?Wg93)AkRd&ZOX(6pz>&Umw47vp9=VFdOkjNlbrSA14O4?GO>E!@VH^;0e% zaQRzs1t?blxPmRXf|M%+T;Ud6Aq^6p^3@SRB}z{W?#%@tKXd(@xy4+8lyOPhwritVp0dcU?@g z@GBN4bv8$)B;9xQ2K^24e7=~IB%YV>!Bi?$dC_xdx;!Hunwyv_$#aLqn_?b@PY&HE zldIBfE-xO+mx*}DfJ@KpJlO&dE`*Jl?ZJ12p+Ncw$p2KB^;rAu`3=SuJoxXOyO-Jf zLACR3t@G{0f!jmtf#lcTpZah6Z*xn664-Zl5=j03CN#m=<{Rf8=XA3Th<&z484`!W zw2QRE4}821_>u5|6xj|`_rn-v!t)DJv#Vwvo01SvW@nW^C+UK0I;IDB9w(CL^$^d) zfai*s=6L?vT&`%;5bTZ~JUyKwX9{9bpl{cMqwpz=e1g_z#G5kB>tR8Z@}w{;7s@3) zGLVzSOkqYe>Oy%ULV>{NWIC&47d{*@zfoQ+7I_{9xD86_$WGCb5kw3n2uJB25SV!D zZ#~?#_`VkIT9%aXK{b3(3m>cuR114oWF_3IhI_SeFBB*CF74M6dsmW5qDM{iXo;T6 z+4cCor4B8=Z>?8}A64T=wfND>XB(`?yMKdeLP9R@Hu&Bx2%TNpUz=vBu0+3r0o3%$ z6zGy)y0RXSd-jUO!-zh}RV77muX-Ho53DRU{-BMYZQzI112_=5^n$ewHH%$48LNUH z^lHFS6A^--X`(_1v`knx0fxHsLb)iWJ)m1SXbkksanuM`GbQ2T?-JhQ>7xI8rV?qpS>Ap=wb?qAo@O=a~jNS91-4HHo>^4Q2AxCDbfdgJK^3$ zDYq~VlDoZkF@}G}10O=AO>y9|LlwtokgzL`E7qWxUjWqV)$|p7f?iPLh8k3nR^D^Z z2LonDUU*XPq+YmF2VS_+X)ien^?CqDi?d5UfGkxBS#h4AVCnwT{iCP*2L|aN46Q=@zZWWv{^PvTPLq?ELN#fqptmuF1V4+k z2Bh|FHr#B|x4~GXGQ1IDxSgn!cHGUZjII9YNskg9SHt64cpP+4WY^-D7TI<8`dY_# zyPg!4$YnKhS&Lk*oT+LpvUWlVpHjo8wD76Qzy|B`{t#7;lRU$+=d}u-%lnxN8JkgK zFgOGo;w_D{S%I1w;;9uNG*Iw7!Sm~2=G zPzxqDRFXxK%j5EqnV%K)c%fFn%TOYbL(tWy0uT}eVjGTy^_L59((wW+!JrhXNFE`_ zv1tbYC^F7L#UH@kAe=vFDV#WuC~BLBf!Kx96iKVwb=DP% zswO?OS1*;GA!J7K7TnS;AT{Z+%Y!nKQz8>;WI~Hf)ColW+K%Gz4x-#iP#oStaWE@D z9Nt4dCov=eyJLgS@0%n<2g;=!iUF(((`7+#6U;n6LkkQ!AZKA5zm6QV+nc9iKt`ak z7Wf~B>UZ&d=aEqD*sv145!lHeV#61ZG(~rZqthn1d+~u$t4|{&gGq+~k6{@STykE4 z!9P+WIW>~gBDvo#2C+N8*BImmi(*X`N5Q_LxjH@kYvV6o-}Gyt54}jLntxa5OWL|@ zVvj1lS77f;=s7`x*4-5tXQ&_MJ zyfs@u^h2&bp20f&6pUwh>*5(+!MEyncoz_}vhf5~gAVHfJd;;&K&;Bf5o+=th*a5l z0!?@zPG#e1)?bKH*?7WD+JP7q@Sqz(FM$&xf)Dtj4?sbFi%ODS+4k4uOTNZCw@@C8 z3(yPR7IVzwR!1w%W}x#mc(zdX`cHN>?GYRnq+zjRd%%AqX7JmWf;_P3fW0 zY8oBXQd#C}wF}XavYe{pKv$P7l}n<|jsS(6_XY&qq=on;{7L0f^Eo1HxIdqAF|o%<986vA9Is3#Pr3bLd}1ukr%EM3_CPgAd*Z&OL`EZZ$`5DiXNd~s>f8O0=!B>SQ%C-7^c1O zzdUe%#7HTDUc)|3?{V0tdTjLK=bxXykgn0s39|f_$0=6J8;EUt= zTuCTk(!#19MBMR+JF*{pk8Vm+Pj&(;ko3bX{Wo|XHCr4!*{$fwnxt}mBgAgQ0+T50 zmJN=H?yh%WrXUF8nvhW_IvZviJCUcPgAK;hCVm)5v+ z-K*bQ6O`C-HFjK!9f#(vnQ;_%9=Pk%IuEP}kGhr4kJQeOw9b!Ume-(yYU^k7NM>W;fCdu``yG*hnxT4C-JlgHs+j?xfa|tD{=Wf% z2hdh6(2v0Y$0mm}T!1D){WeImW!T*Q5Q3n3LOKVE1oZg@5u*2k9tBy391rpYS<_Xb z`V>_u)v*~;g5(>kJc4qB>j2drda`OG*Tun`tG;A?k1Y*21FcEuSs5DeZ6$UB{+E;5 zo?}K*37jxhVAP=k>(m<+a_XWlK~rQAh#sl+3={|1ixq!CD`p9-A!MGP%@KLNsnYlw znUPGv?KEIbF}N$PTwndMGW?~&eWh|=Y1~&J4ax4MGg`7+P43r{`zz&{!p|8xqTwV1e%O`f@ROnD4dKT^ z6Z8@Tpa#+KqZjN3yB+p(5Kk&(LxLBSpifWG5BE99aUoxrrO_ijieg>O8VCb5g>a)1 zU#og!oGq}aoD4W4sC9HNeMfVIZEx(T1-CzeXY1ew8n6fcm%HzqDJ9TjD!}l|6(B_m zh1)VTN9`Tjsix5S23e5I!!2C~QU|S8+Rafnp>TPX%WGV|GKlCogm7^Z13mb*=IHrH zsCh&5;5tXIrps;(TOq(!ZATDKD|l>Cee}9LS5O1dOK7$TgBk<;#mq%GV8!S3pcSn& z0EaFPM!%^)@-zggr*0qsm1|f^fY{K7RtR*4*~HM^tDFzIcZJQ-3!D)U1`M0y%1rJ8 z0k?-D(wCbl!=}HXc$x+n+pGatX#7)nT@uO=>q+gBnYmxrS z8ASJoh;An_(2ZScj_!Yjnm0r@3r*0A>P9r@=tWQ4*zt>d-gO9fNJ9g<8y0Ge7kRGW z`4{UAen-@yjiNtpqCZ@>_o8!k`&On5ko(Y%;nh#aQ5e|g zYX0GrsHfk2s zc*563a6rt*!()&!>+~=!r0+4r2o4AxhWVp`F@np3ZH^(HL9Z4Yz>eP2y$8!?sX0j~ zJT11u{t4c44{n0zz>M$xA*5`J7}{;1k(f=~R(qXAY}pNr4VPVS8OvMRT;3Mk$5N)e zlV8D;dIy{=G3Jp@84s}~hz-s+x5BnMr@{=wEkjEZ?y^@Gy)h*=hEC{v@C*NI^1t<6 z_?Ny5YF|d{gHR8aDSbB&ZM?k7sPh7?w#Cwgb**z>0^V8^dRPa!%p0Dwox`EV``w&}H4XTzoY7 zxTGY{smXI%@*McCmgjIm`QWS)A6DbTT6`FfioGPj(&Tu?C3-xg88sk;Ll{Z7gY|SJ zSDGsK2ouzW(&J`+PA(Ls9vKeX5f)hW?Phtt3|38Hq6ZFgPWKom7mXt?-g9{B)!4Gf zBLH+4o^FHE{V2mQ{u(Q&#Y`<>T)nA^r8DaA!(IeV7zYJEB>xP2C~nd;z;?sqa=F%- z9g6*bo!O_@|JRv=iv53s&2u8}N`-Tv|!mOrmm?ufeZbW3x(GYC9sh!?Z5K_oQM-R!6rvC>O%ZCr^Xk-I{bjm_^6!xAKzncxBvhE literal 209 zcmZ3^%ge<81k-=yXIcX3#~=<2FhLogg@BCd3@HpLj5!Rsj8Tk?3@J>(44TX@K?*b( zZ?Wa(r=;c-`)M-W;!Md(%uCPLOGzqX21>4E_zY6>OHV%|KQ~psG^sSNq*On(A~m_R zB)>?%JijQrxF9h(RX;huC{-U~j9x+IFAf_ZyEG@&u80Guoe_wOWr4&8W=2NF8w@fR Ku%RM0pb7xLi8T)Z diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659f6c6e0ae848e54157af197c543a09315f..d1c2c2447e3afeb29b80b7b2e3cf3bc8144432f8 100644 GIT binary patch literal 2307 zcmb7_O>7%Q6vt=n=X^Pd^Xa5*2x;@Jl5E;4QlM!;B_K-D0)i+}LSipmK!dd|pwKYvcIDwOeL@~LR)KCHu4FgF&fv6Ct z{%rU)iO?V76hdeF*EjGX1jB(7mt6ACMRA>Xi}@duHo#w z)PhB7&m{Y$3aX?`3zGwxwu~Zihf1VC^3GJ=DDE2Owx!RTmh+B7EW>hgCcdv%G69i` z2)A+1b_gx$l@@=JE0`t6&4xk0Jc#?%t-%}<$1n@-H8n-B>BQ~i!W~nCLK~q*-Z5=U zH!v3B#F(pzx??qqU@tWRe?!5|I^X>;`e52!!zE3Llv$?vgJEJP`zn67f z%{Kb|ZK!(P{vW-fyE=}0K9!zLrQuTPs8m`e zm7YkY=~3xwRN5AmzC@+*Q0XjGS_hThK;<72&B~!q4*g=Nq{W@Y!HHjvRr(#h5+U^Z zEq%u>61}`tww$s~4oJT2*i_%Q>C@7#ktcfIrXnpQPVXMRZkjLJxLhFj>7Xc=FpX%}dK)juy#H>-bMHlS4V@xn>h$@2H>KkYLZ_h6jE_ysf_FRY-md}8kW8wN`r zEOA)+E9iggY?j~oh<)-E1H%J@gAo?^iHGddEe3fH@*MJE;?CJJpMAu>*kNFLU~(|S zR?}xGKJ(!DAcIv8RynMO1^yP^d-g4Z6%ST8tc3BY(=yi=*y5KAHaytiun{(zIUD7- zKV*+K7;Jj5$ze0B(9e4LEs@w{kn zLk@>w#ohB|es7&^i32(E;E2Ofn7DUd=I`b%5!%+?Pl&hFLgH_K{Kp?JZ)P)zBk@r46s5OpKTZ>YN#Zqu&!ZS#>~L58i*kvoiU0f zg*})-lj9{wK$GznOF?2u#!E&hCnG;M^%g@B$Q+PdUSe*lpC;=q?v$*=y!3p%(xRN= zB9LLXSj#d~%ZrOxC+}kmh~Wc@fJ`X%0umpX85tRGFvwg$MGqLnFQB3iYz(ZN4K5u* f9U(K=F0ja7WRbtZBL9JziJz%~8w87ZfW`p;$AmgK diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd69370b38a98d8b01bf8eb9817c42f16ed6..2b1d115531610b547c1056ae9fcc5abbba1bd376 100644 GIT binary patch literal 17942 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 literal 1364 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 Cj!FXn literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0002_donation_event_eventparticipation_interaction_and_more.cpython-311.pyc b/core/migrations/__pycache__/0002_donation_event_eventparticipation_interaction_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c1585095673e8c88b99ee401f21aeb880028811 GIT binary patch literal 4600 zcmb_gJyaW47M>YN1Ns3ZTQ(MoL6(UVffo_mf%Ptn`7t;Ub00r*o#@ylT(cicUVJN|U|sjea18@RN+BJMZVd z`|kJM`+ne`EiC~7p0P)^w%8^J|6`Tf{+Re5jH)7@Gbn@XTgWWc_HQ7W!0IKL<6f@)>LiH&@C#j8VKjCOjVb^ zBGt;ZR6N3AL8m z4srwb!UydgYYighJ}83DmCpavUe;HN9`qUP?`Q17ugKU%?t8fIQG4mqrU-n&&wT)2 zjO)~53VfA1-qDGlnS=Mx5c?u4?&g@$PU3u5HUl8x)q4D+{6Ys01z~#7e$_Sf;h~($$8efD1*bh6>(RSTFYo_(POtw&$BWuazd5+d zf6-3TcPI{ zLACK5H$11BwwBTIF1c<>Qas(rgr7A%Q2WtI&M^2+tDfsWG?;q=rXvL?X75`?D&4K`c_AO&4} zQoxFBWFaZB37Q>DbBN93F)y=nCkWbP@NBSVQs-&2!ZS=!Xh2cm23g2qD2Ei~NkPrA zbYnGy^H@h%&uHK#yN%M|IA2_!vMZ;_ZiX~8re#NA*-f(Kr2Yv=Ubh&UaF>r;3rxa7 z(X#p{uoh*sqUu?LN@$Tb^92GqLYwFBK2&BtyMLcHjgQWck51gB{_$nitQvlZ*X^n) zBM*tiFTej5-hvOp4|;kT0+22&!Wu55@jA|cM`rq|VLrC`+YZt6L#s5%0oV72FqCcC3@!7j~K8(iC8)ISwj@L#ETq^SJjekCCIyZ#<< znmczQ@&86HkoKOh)|~d9ZSU)DvY-&%B<)tY-E!J361I0D?If0ZdC`fb%CTN2)=R?I zcOo$o?|V7m#QV0@*JC6;TaM2<@mUg{<6YNT*Y&OLSJ%EzIb#o;vA;NDh-lA9{CPS4 z+=)N02ts63+KF5u9lbBFIUT)QOW$eEz@#%U>kNEOmY#sZQ*m5^SA0Tx!{r{AY~)?3 z5|pE@6`$W9cq4$=W1kr#Neq$jANHeN`>yB=PC0{f&fpg$Yk;E#af~5&Z=4|j2?#(k1QW+1c>e?l zj$&^Orub6)BWG`#vp3D=j!*Ns@BIw#U&6>SX@-xS3~GQu z1+m9O7)!#u@(H8CD2-N|Nj4_b|D72llrMt(8cSg$%?V6BR5DpOatQV;}QDZHG+BH0JL2eQ2@I z{a;02ZPPcWK@Dz1{5*hue#r21#oc26#H?^r^HBPsdB{DfhRsJ%Wq%@DIS>_(D2lM{ midTq`aKAgkhvcwtMV=5vxgs1czX~|v?l-)1?+6v{&Hn*Ea*M?P literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0003_tenantuserrole.cpython-311.pyc b/core/migrations/__pycache__/0003_tenantuserrole.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7827bf9b337969a1ccfa3b8fca3a8a8110757860 GIT binary patch literal 1869 zcmbVMJ#5=X6h4v?^=sL3Wvfp5le7jB8L4H&bzPu^8^wQV8@sM$2dNf<)}19PA}Jp! zC-P8u=+H4+Gk0)X*h7X4U9xv%01n1Y1v+J;VNaR*?r5q>6LjcNe0uNhd+*-8@9};a z7)T=+Yj<7aX$+yixDk!`xpKM&%1;O*EVWThf`7R!n@UZQ_?*&?nekfORBNh)WVDQM z><5J7B;HCyfesXe4&e<)ErF#fDyh%8xmuD~(jl&ESPiEnv$Sb6sO}oJF=;#X zb3gHyJvUC963>Z#z}$9M7|A#Q$I^MeUv$n0SRw70GSoYC|^VdTDfSomFjDQeQhYx z@Bq#o4Zd14h&S}Vp(#S5c+)#FO>Kb*U6D}B*OQ>GwoNBs-HIqB%Hi)HL?xsWNy4@yBHt@z0 zlfPUj#aLGFy0%uQM0W{hnGQ8fo%XbQq{mW#H}tl#M_6{3&@I~`nx&hB#VAB6DUe*& z={YVjH65FVwO6PL>r#bVhT}qS*%VN@x^8xKqX8)i-01wd{U1oj)wj3za(!f#+s_NY z8w5<$o3>FW4s@{oKx-4L;WnA-ic_+57xLD4iq2k!7jH6LC@CxjRe>%iE}V#xHa~KB zBH6WBjQg={2oC+(VAnEsxgm%I3x` zZL_+#uC08tuy~tg`v?OHkeL_Et?-YR@;iiOZyJpXaJt0vVX671PD3@l1hoJXz6NSl z+|sRv&1AgAlA6N)T5H>i+E@i&F3NwfBzmm;cOJ^=5 zMaM5X-jI8oFM4C8N2C5&DL`m?NB)=mUcRBg)P;(2BIKEnFq6Nj9OuWpi`O1~;$OV> zc=t)qpI!E6*ZkSLUc&)EPg>%^-dUEx6C`+oi`zC*S!qi~h{2KXb<; zp=X7od(yWYeqo&l0Liyr7Xhkrkct;_K@R0V@W$u755EW$c_bI)GpY0m0&&JgpbQMF z`(N>gPDceTCD~X=>m=Tq46pOah+Fy*z*y#dy>46EVSA{Dya25P>2|67VKn_Ryv9cN{4%&0O`+8-9UWkkkOZE*_@+q45*GaQ22( Gk=5VazyJaO literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/__init__.cpython-311.pyc b/core/migrations/__pycache__/__init__.cpython-311.pyc index 9c833c8090679ee4c9ffd863c8f7abb0bb73a5db..154497be0bba1349f90ee99d109dd3a111a8001a 100644 GIT binary patch delta 19 ZcmZ3%xPp;;IWI340}y-&DxSza7XU811s4DS delta 19 ZcmZ3%xPp;;IWI340}xbw%b& - - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {% block title %}Grassroots Campaign Manager{% endblock %} + + + + + + + {% load static %} + + + {% if project_description %} + + {% endif %} + + {% block head %}{% endblock %} - - {% block content %}{% endblock %} - + - +
+ {% block content %}{% endblock %} +
+ +
+
+

© 2026 Grassroots Campaign Manager. All rights reserved.

+
+
+ + + + + \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..7be5adc 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,64 @@ -{% extends "base.html" %} - -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% extends 'base.html' %} +{% load static %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… -
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) -
+
+ {% if not selected_tenant %} +
+
+

Welcome to Grassroots

+

Select a campaign to begin managing your voter database and organizing your efforts.

+ +
+ {% for tenant in tenants %} +
+
+
+

{{ tenant.name }}

+

{{ tenant.description|truncatewords:20 }}

+ Select Campaign +
+
+
+ {% empty %} +
+
No campaigns found. Please create one in the admin.
+
+ {% endfor %} +
+
+
+ {% else %} +
+
+
+

Dashboard: {{ selected_tenant.name }}

+ Switch Campaign +
+
+ +
+
+
+
Voters Registry
+

{{ selected_tenant.voters.count }}

+ View Registry +
+
+
+ +
+
+
+
Quick Voter Search
+
+ + +
+
+
+
+
+ {% endif %} +
{% endblock %} \ No newline at end of file diff --git a/core/templates/core/voter_detail.html b/core/templates/core/voter_detail.html new file mode 100644 index 0000000..25aaebb --- /dev/null +++ b/core/templates/core/voter_detail.html @@ -0,0 +1,782 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} +
+ + + + +
+
+
+
+
+ {{ voter.first_name|first }}{{ voter.last_name|first }} +
+
+
+
+

{{ voter.first_name }} {{ voter.last_name }}

+ +
+

+ {{ voter.address|default:"No address on file" }} +

+
+ Voter ID: {{ voter.voter_id|default:"N/A" }} + District: {{ voter.district|default:"-" }} + Precinct: {{ voter.precinct|default:"-" }} +
+
+
+ {% if voter.candidate_support == 'supporting' %} +
Supporting
+ {% elif voter.candidate_support == 'not_supporting' %} +
Not Supporting
+ {% else %} +
Unknown Support
+ {% endif %} +
+
+
+
+ +
+ +
+
+
+
Contact Information
+
+
+
    +
  • + + {{ voter.email|default:"N/A" }} +
  • +
  • + + {{ voter.phone|default:"N/A" }} +
  • +
  • + + {{ voter.registration_date|date:"M d, Y"|default:"Unknown" }} +
  • +
+
+
+ +
+
+
Likelihood to Vote
+ +
+
+ {% for likelihood in likelihoods %} +
+
+ {{ likelihood.election_type.name }} + {% if likelihood.likelihood == 'very_likely' %} + Very Likely + {% elif likelihood.likelihood == 'somewhat_likely' %} + Somewhat Likely + {% else %} + Not Likely + {% endif %} +
+
+ + +
+
+ {% empty %} +

No likelihood data available.

+ {% endfor %} +
+
+ +
+
+
Campaign Assets
+
+
+
+
+ Yard Sign Status +
+
+ {% if voter.yard_sign == 'has' %} + Has Sign + {% elif voter.yard_sign == 'wants' %} + Wants Sign + {% else %} + None + {% endif %} +
+
+
+
+
+ + +
+ + + +
+ +
+
+
+
Interaction History
+ +
+
+ + + + + + + + + + + + {% for interaction in interactions %} + + + + + + + + {% empty %} + + {% endfor %} + +
DateTypeDescriptionNotesActions
{{ interaction.date|date:"M d, Y" }}{{ interaction.type.name }}{{ interaction.description }}{{ interaction.notes|truncatechars:30 }} + + +
No interactions recorded.
+
+
+
+ + +
+
+
+ + + + + + + + + + {% for record in voting_records %} + + + + + + {% empty %} + + {% endfor %} + +
Election DateDescriptionParty
{{ record.election_date|date:"M d, Y" }}{{ record.election_description }}{{ record.primary_party|default:"-" }}
No voting records found.
+
+
+
+ + +
+
+
+
Donation History
+ +
+
+ + + + + + + + + + + {% for donation in donations %} + + + + + + + {% empty %} + + {% endfor %} + +
DateMethodAmountActions
{{ donation.date|date:"M d, Y" }}{{ donation.method.name }}${{ donation.amount }} + + +
No donations recorded.
+
+
+
+ + +
+
+
+
Event Participation
+ +
+
+ + + + + + + + + + + {% for participation in event_participations %} + + + + + + + {% empty %} + + {% endfor %} + +
DateEvent TypeDescriptionActions
{{ participation.event.date|date:"M d, Y" }}{{ participation.event.event_type }}{{ participation.event.description|truncatechars:60 }} + + +
No event participations found.
+
+
+
+
+
+
+
+ + + + + + + + +{% for interaction in interactions %} + + + + +{% endfor %} + + + + + +{% for donation in donations %} + + + + +{% endfor %} + + + + + +{% for likelihood in likelihoods %} + + + + +{% endfor %} + + + + + +{% for participation in event_participations %} + + + + +{% endfor %} + + +{% endblock %} diff --git a/core/templates/core/voter_list.html b/core/templates/core/voter_list.html new file mode 100644 index 0000000..b4010e1 --- /dev/null +++ b/core/templates/core/voter_list.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

Voter Registry

+ + Add New Voter +
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + + + + + + + + + + + + {% for voter in voters %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
Voter IDNameDistrictSupportYard SignActions
{{ voter.voter_id|default:"N/A" }} +
{{ voter.first_name }} {{ voter.last_name }}
+
{{ voter.address|truncatechars:40 }}
+
{{ voter.district|default:"-" }} + {% if voter.candidate_support == 'supporting' %} + Supporting + {% elif voter.candidate_support == 'not_supporting' %} + Not Supporting + {% else %} + Unknown + {% endif %} + + {% if voter.yard_sign == 'has' %} + Has Sign + {% elif voter.yard_sign == 'wants' %} + Wants Sign + {% else %} + None + {% endif %} + + View 360° +
+

No voters found matching your search.

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 6299e3d..9712077 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,26 @@ from django.urls import path - -from .views import home +from . import views urlpatterns = [ - path("", home, name="home"), + path('', views.index, name='index'), + path('select-campaign//', views.select_campaign, name='select_campaign'), + path('voters/', views.voter_list, name='voter_list'), + path('voters//', views.voter_detail, name='voter_detail'), + path('voters//edit/', views.voter_edit, name='voter_edit'), + + path('voters//interaction/add/', views.add_interaction, name='add_interaction'), + path('interaction//edit/', views.edit_interaction, name='edit_interaction'), + path('interaction//delete/', views.delete_interaction, name='delete_interaction'), + + path('voters//donation/add/', views.add_donation, name='add_donation'), + path('donation//edit/', views.edit_donation, name='edit_donation'), + path('donation//delete/', views.delete_donation, name='delete_donation'), + + path('voters//likelihood/add/', views.add_likelihood, name='add_likelihood'), + path('likelihood//edit/', views.edit_likelihood, name='edit_likelihood'), + path('likelihood//delete/', views.delete_likelihood, name='delete_likelihood'), + + path('voters//event-participation/add/', views.add_event_participation, name='add_event_participation'), + path('event-participation//edit/', views.edit_event_participation, name='edit_event_participation'), + path('event-participation//delete/', views.delete_event_participation, name='delete_event_participation'), ] diff --git a/core/views.py b/core/views.py index c9aed12..90f37ba 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,270 @@ -import os -import platform +from django.shortcuts import render, redirect, get_object_or_404 +from django.db.models import Q +from django.contrib import messages +from .models import Voter, Tenant, Interaction, Donation, VoterLikelihood, EventParticipation, Event +from .forms import VoterForm, InteractionForm, DonationForm, VoterLikelihoodForm, EventParticipationForm -from django import get_version as django_version -from django.shortcuts import render -from django.utils import timezone - - -def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() +def index(request): + """ + Main landing page for Grassroots Campaign Manager. + Displays a list of campaigns if the user is logged in but hasn't selected one. + """ + tenants = Tenant.objects.all() + selected_tenant_id = request.session.get('tenant_id') + selected_tenant = None + if selected_tenant_id: + selected_tenant = Tenant.objects.filter(id=selected_tenant_id).first() context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + 'tenants': tenants, + 'selected_tenant': selected_tenant, } - return render(request, "core/index.html", context) + return render(request, 'core/index.html', context) + +def select_campaign(request, tenant_id): + """ + Sets the selected campaign in the session. + """ + tenant = get_object_or_404(Tenant, id=tenant_id) + request.session['tenant_id'] = tenant.id + messages.success(request, f"You are now managing: {tenant.name}") + return redirect('index') + +def voter_list(request): + """ + List and search voters. Restricted to selected tenant. + """ + selected_tenant_id = request.session.get('tenant_id') + if not selected_tenant_id: + messages.warning(request, "Please select a campaign first.") + return redirect('index') + + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + query = request.GET.get('q') + voters = Voter.objects.filter(tenant=tenant) + + if query: + query = query.strip() + search_filter = Q(first_name__icontains=query) | Q(last_name__icontains=query) | Q(voter_id__icontains=query) + + if " " in query: + parts = query.split() + if len(parts) >= 2: + first_part = parts[0] + last_part = " ".join(parts[1:]) + search_filter |= Q(first_name__icontains=first_part, last_name__icontains=last_part) + + voters = voters.filter(search_filter) + + context = { + 'voters': voters, + 'query': query, + 'selected_tenant': tenant + } + return render(request, 'core/voter_list.html', context) + +def voter_detail(request, voter_id): + """ + 360-degree view of a voter. + """ + selected_tenant_id = request.session.get('tenant_id') + if not selected_tenant_id: + messages.warning(request, "Please select a campaign first.") + return redirect('index') + + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) + + context = { + 'voter': voter, + 'selected_tenant': tenant, + 'voting_records': voter.voting_records.all().order_by('-election_date'), + 'donations': voter.donations.all().order_by('-date'), + 'interactions': voter.interactions.all().order_by('-date'), + 'event_participations': voter.event_participations.all().order_by('-event__date'), + 'likelihoods': voter.likelihoods.all(), + 'voter_form': VoterForm(instance=voter), + 'interaction_form': InteractionForm(tenant=tenant), + 'donation_form': DonationForm(tenant=tenant), + 'likelihood_form': VoterLikelihoodForm(tenant=tenant), + 'event_participation_form': EventParticipationForm(tenant=tenant), + } + return render(request, 'core/voter_detail.html', context) + +def voter_edit(request, voter_id): + """ + Update voter core demographics. + """ + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) + + if request.method == 'POST': + form = VoterForm(request.POST, instance=voter) + if form.is_valid(): + form.save() + messages.success(request, "Voter profile updated successfully.") + return redirect('voter_detail', voter_id=voter.id) + return redirect('voter_detail', voter_id=voter.id) + +def add_interaction(request, voter_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) + + if request.method == 'POST': + form = InteractionForm(request.POST, tenant=tenant) + if form.is_valid(): + interaction = form.save(commit=False) + interaction.voter = voter + interaction.save() + messages.success(request, "Interaction added.") + return redirect('voter_detail', voter_id=voter.id) + +def edit_interaction(request, interaction_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + interaction = get_object_or_404(Interaction, id=interaction_id, voter__tenant=tenant) + + if request.method == 'POST': + form = InteractionForm(request.POST, instance=interaction, tenant=tenant) + if form.is_valid(): + form.save() + messages.success(request, "Interaction updated.") + return redirect('voter_detail', voter_id=interaction.voter.id) + +def delete_interaction(request, interaction_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + interaction = get_object_or_404(Interaction, id=interaction_id, voter__tenant=tenant) + voter_id = interaction.voter.id + + if request.method == 'POST': + interaction.delete() + messages.success(request, "Interaction deleted.") + return redirect('voter_detail', voter_id=voter_id) + +def add_donation(request, voter_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) + + if request.method == 'POST': + form = DonationForm(request.POST, tenant=tenant) + if form.is_valid(): + donation = form.save(commit=False) + donation.voter = voter + donation.save() + messages.success(request, "Donation recorded.") + return redirect('voter_detail', voter_id=voter.id) + +def edit_donation(request, donation_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + donation = get_object_or_404(Donation, id=donation_id, voter__tenant=tenant) + + if request.method == 'POST': + form = DonationForm(request.POST, instance=donation, tenant=tenant) + if form.is_valid(): + form.save() + messages.success(request, "Donation updated.") + return redirect('voter_detail', voter_id=donation.voter.id) + +def delete_donation(request, donation_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + donation = get_object_or_404(Donation, id=donation_id, voter__tenant=tenant) + voter_id = donation.voter.id + + if request.method == 'POST': + donation.delete() + messages.success(request, "Donation deleted.") + return redirect('voter_detail', voter_id=voter_id) + +def add_likelihood(request, voter_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) + + if request.method == 'POST': + form = VoterLikelihoodForm(request.POST, tenant=tenant) + if form.is_valid(): + likelihood = form.save(commit=False) + likelihood.voter = voter + # Handle potential duplicate election_type + VoterLikelihood.objects.filter(voter=voter, election_type=likelihood.election_type).delete() + likelihood.save() + messages.success(request, "Likelihood updated.") + return redirect('voter_detail', voter_id=voter.id) + +def edit_likelihood(request, likelihood_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + likelihood = get_object_or_404(VoterLikelihood, id=likelihood_id, voter__tenant=tenant) + + if request.method == 'POST': + form = VoterLikelihoodForm(request.POST, instance=likelihood, tenant=tenant) + if form.is_valid(): + # Check for conflict with another record of same election_type + election_type = form.cleaned_data['election_type'] + if VoterLikelihood.objects.filter(voter=likelihood.voter, election_type=election_type).exclude(id=likelihood.id).exists(): + VoterLikelihood.objects.filter(voter=likelihood.voter, election_type=election_type).exclude(id=likelihood.id).delete() + form.save() + messages.success(request, "Likelihood updated.") + return redirect('voter_detail', voter_id=likelihood.voter.id) + +def delete_likelihood(request, likelihood_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + likelihood = get_object_or_404(VoterLikelihood, id=likelihood_id, voter__tenant=tenant) + voter_id = likelihood.voter.id + + if request.method == 'POST': + likelihood.delete() + messages.success(request, "Likelihood record deleted.") + return redirect('voter_detail', voter_id=voter_id) + +def add_event_participation(request, voter_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) + + if request.method == 'POST': + form = EventParticipationForm(request.POST, tenant=tenant) + if form.is_valid(): + participation = form.save(commit=False) + participation.voter = voter + # Avoid duplicate participation + if not EventParticipation.objects.filter(voter=voter, event=participation.event).exists(): + participation.save() + messages.success(request, "Event participation added.") + else: + messages.warning(request, "Voter is already participating in this event.") + return redirect('voter_detail', voter_id=voter.id) + +def edit_event_participation(request, participation_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + participation = get_object_or_404(EventParticipation, id=participation_id, voter__tenant=tenant) + + if request.method == 'POST': + form = EventParticipationForm(request.POST, instance=participation, tenant=tenant) + if form.is_valid(): + event = form.cleaned_data['event'] + if EventParticipation.objects.filter(voter=participation.voter, event=event).exclude(id=participation.id).exists(): + messages.warning(request, "Voter is already participating in that event.") + else: + form.save() + messages.success(request, "Event participation updated.") + return redirect('voter_detail', voter_id=participation.voter.id) + +def delete_event_participation(request, participation_id): + selected_tenant_id = request.session.get('tenant_id') + tenant = get_object_or_404(Tenant, id=selected_tenant_id) + participation = get_object_or_404(EventParticipation, id=participation_id, voter__tenant=tenant) + voter_id = participation.voter.id + + if request.method == 'POST': + participation.delete() + messages.success(request, "Event participation removed.") + return redirect('voter_detail', voter_id=voter_id) \ No newline at end of file diff --git a/db/config.php b/db/config.php index 586c03f..40e9d76 100644 --- a/db/config.php +++ b/db/config.php @@ -1,9 +1,9 @@