From 56126df7d4dd31af8d0fe963472dd2ea9b393899 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 24 Jan 2026 04:08:52 +0000 Subject: [PATCH] adding track reminder --- config/__pycache__/settings.cpython-311.pyc | Bin 6502 -> 6542 bytes config/settings.py | 1 + core/__pycache__/admin.cpython-311.pyc | Bin 9080 -> 10398 bytes core/__pycache__/middleware.cpython-311.pyc | Bin 1280 -> 3238 bytes core/__pycache__/models.cpython-311.pyc | Bin 28164 -> 28883 bytes core/__pycache__/urls.cpython-311.pyc | Bin 2287 -> 2398 bytes core/__pycache__/views.cpython-311.pyc | Bin 25299 -> 26302 bytes core/admin.py | 21 +++++- .../commands/check_subscriptions.py | 57 +++++++++++++++ core/middleware.py | 44 +++++++++++- core/models.py | 12 ++++ core/templates/core/subscription_expired.html | 53 ++++++++++++++ core/templates/registration/register.html | 68 ++++++++++++------ core/urls.py | 1 + core/views.py | 28 ++++++-- locale/ar/LC_MESSAGES/django.mo | Bin 26140 -> 27110 bytes locale/ar/LC_MESSAGES/django.po | 36 +++++++++- 17 files changed, 291 insertions(+), 30 deletions(-) create mode 100644 core/management/commands/check_subscriptions.py create mode 100644 core/templates/core/subscription_expired.html diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 8d9d4560b24e4169423369048228a93d575076ef..590b1823e2dd612da3fe19cfe0707f42e720ef6b 100644 GIT binary patch delta 102 zcmaE6)Mw1QoR^o20SJD%lw?la2;?wwZkFcqWRz7)&M!*U%gszl$w@6w1Tuq5lZumz wG7CyF^YbP@YddH9$ delta 64 zcmeA(erCkGoR^o20SFQn6lW%H1acTTHcNAPGH&kS=4WNxuz8NaF;+otKBf;0FrtC$ LgVg3?F==K18*LC; diff --git a/config/settings.py b/config/settings.py index be01bfa..54296d7 100644 --- a/config/settings.py +++ b/config/settings.py @@ -69,6 +69,7 @@ MIDDLEWARE = [ 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'core.middleware.SubscriptionMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Disable X-Frame-Options middleware to allow Flatlogic preview iframes. # 'django.middleware.clickjacking.XFrameOptionsMiddleware', diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 91bf8044db4a5ee19d01c8c3f0f926b458fbe33f..c4cf650d59359192b707a7c510b9760554718907 100644 GIT binary patch delta 3395 zcmaJ@Z%kX)6@Sk*{)7Kv>;kqi*kA(YkC#Lve;UG|kfbPymyEOoI>~+bJ%iI{KXTtQ z1VS)dGc~I;t@NthrfE|?tliRTTUEa7Q>SX3v`yM%BBLR1lk%k>)-NM#KTK8k=bZZt z1<%{F-|yXf&$+*I?!D)pd;f9%hgV%cb~@`hcs?4}GN$GYR}263jqXG%Ct74j);Z(k zIgt~svMcMJaYNZA*2^2;jKs748Gkk~6UYWFfAIoJdr9{3zhd}9aS5`52LbfIlH17+JRJt_qAzKSb{Ckpt?keL?yvcLi+wkg-fxEzo0q7Cj>tX4saNRd{ zRR$NU;t0^sjC%v`t&#<4!c>tB^i|1PYuN4p+nxKi`^D%a7we+0o2~_8eA%jHveJr@ zlb~!*DrS3iO&`7AEAmmj%^6dFqTM|*~DrJ-u=f|DSbRb zk6DAgcexoeOtVF-J3P6PIceSNBbs4SE%b`Djjme5gI+a1tENb1LCYw)q;zW`Lzc^j z8&yrv@@mXTz6c!T1OWZM)gL#MU3pa^$s{5UkPLz-W!N(WUo?s?e!RldpvUhCX?qSs zqiE3T=+`^}`fra5d|Ku28|hcw9gXC$mI<^58V_ihK&vxkST%^|7g*?${zJ4D=Z&=*iTykbcx;VZI`TRBlnok-%Da9YV}14~k7ZeDvbJgdm! zL4va79Il2W{?)>91#V@u7L7uL+?@2kn8H1`?}TsPiS4=MWQ>{ITv%09r?Y(Mwwo4@hDzxu)ImiJo8d#&iW_Q>w~_VCNYTlQed9^B=4+Z@j{r?#hO ziqDB#(^6?#+VZAL-gMECe&lZaqdWT09o=#tDY=i_E0}id?pKbzeEQyL)@ycKl#1#6 zmbh3F7mK%6x7P~095-sXV0a9R#XoKMME^j|H-QyEP;gEdK6D3)!DqIot{10;t*P14 z)a;f!RdT0_))Y%DpL!<`?8xvW+kA;a%vLjV^uYw^=>w;Yi~^KB>byd+0Nc z9CZ;|;Z-jHtndFGc^B6&?KDoTU)=GXSiih$ve$|HZk%Hu9Ru7z{3oNu_}Etec&UH9 zXlgE*Vnz1XbK`NAZ%1r$^CjReyOpKfp+s?!{>s*UvO2$lsy-Aps7|a#5m;1AU-44i>*gucxK2N;f3~d`i&X@Z*oJVDrRuD`lBy^7Nq%YXvmyzc1XJ|y z_Q9GoNu)tI%V?$@6`CZ_oadjXzi{gx|aW@FCNcU*n^CHiOAQ+$-RyZ>g5fjnj=8f|JANuYAslqa%Uwx{x{reu>? zA-AAiH37huTGwZ96>2N3Q}cqRjxH>WDYnUyqwVDbf5(Mhs|a2Mel zbj=shM^~V4_LKNCf5Yt0bPV?P`KDFBq6$z)0vR5c~Zaxw|WH5)D-1S9%b zWmH9Ynocz3^+=I7KWI9|^RLr?Hpk50L51(q{+8!$ucDEAk($Lw(jd|djWaI z{*;h|jiX;zOynidE7vJRlnBXTxjscCL6gL!pgk~0k$%R9dD^~?Cv_u^a3InsK%z`Sv&5}W{VanP0*q|ZGdKSei;WD_}= zs|~Y4>V-K1S1l2u1rAII4P+ocL-;wuI|vy77#GGozy{au?D-Qgm|!~Y#FRy_RQ9B%&sf3}f7$M15r@s+`nxAQR*e!}pv Xku&&9e9NwZb?Dh_h=>LgPTYS0BPTl3 delta 2258 zcmaJ?S!^3s6!o(m+p*&S;+ zNI;N^$|5A9i~7|<%Tg4OKqy~)(@&5r@&8@6tyO>M! zYwdc{=$-3jF1L%br9P;-Q|-a?E-i$+ez?Mh;weW%#Psm4m0NvGsey z&f|9Nb7>JIi*Ar@9cc0hxA2IeN4(*YY4V7)@aRO3M8hN7*t=MPJsJlUq0xPeMA9h2MSQ#UzSs zf3Z?4TjFeiiDBHYGC|_mRiEf5j}$?gU;x1m=_S)r=1aQ8g&j7zDvFD^<+^5yELq7g zd0Gu^KooTUuoHg>MJmXKmbfDeg zA8*w6VIUQ%SEdvz8e9+LA=TdVf=6s4f(e32go4|S)J!fEg^=Y0HNh}U#p(om8U&YP zV*w(-@z@7n#U@%(93l#WamdH(6o-&vFM9+o#wXJEkiMyxM@UAn4StN*$MeWqtTWHp zG;B}Y=d-;g*XLm=k@CrT;_!N640WF+cC#GZN&FlbLGOZ3Xz)wd&hZ7@*xs7ZO*>Sq znO4P6Xd_A}H@yg%OlDa~CIgi2EZYSyb{}WCwePy)o&k%zJqz3tn`qTxaMM&5xEu}B}2zuSXd<;&APhSrnzJRjQOThb`5rnfPsATeDI#EOnABRf2g0(}U3H z8iy~_BgbTUOJwms6+DM}yWP1KxMd1i(Rp%u3BmkV?iWZ%a2yT}eB7 z9*|v{Q7RX_;tbl^Z55$$A)cajuqe1{aZORJRkyfITqGw`xSSbnd5|(>)#_roL<#}} z?qupid`YIVE4$;UjNM`KBK1wN&Q|$9@x79i z@dO0PID`P6{I(7|aC%X-Oto4So8UmcSE0WM=dzYptGwgnr9yXtJk|1;$_bO7N;XMG zZ#vU|24*m1P3I4aPtlh?vw01!=PmcT8>)kE!t!7zc!z$Q@UW?M*MH%Si5rRhZBqP7 YaNFZ@CvUKxbvKy?*CvNGx0g!&8;XtKYXATM diff --git a/core/__pycache__/middleware.cpython-311.pyc b/core/__pycache__/middleware.cpython-311.pyc index b62f2dae9a5ac0781428c8384649011d4b08ef1c..6acfa1c4bd768d5ee44aaf70c54c18a68641b696 100644 GIT binary patch literal 3238 zcmbUjU2hvjaPNHQuUtNg-6oCW#;B=^+hWtw3e-jw&_MYp;US0;3uIj1*12%L3%h3$ zA_wK65~e59O&4@zMt#cy9Rtc63s$lPXn0NbqJ9Dn)%_X3sWpQVL>kZ*F#G zc6MiHcILk8?Tr#>_a_|VP?(TkaL{f_3)%b@kX51+T{1~o%1IL9vMHAXxq!d}rczdO zs=yU9SPtbv0#`vkoQnuNXhw5Unb7ELPkRBX-H-_R0bal5dS!B+=%Fh_58sf118IS1l2k;Q1A5z8zxlB0+365l# z;?MI(9ERa4p(H2iBq!_A6_N|+GC)NSz{-j@c%il21ie%#y>95bN#_f& zW@r1L0r3F5n{lD8Dpg4yFu&#glG%I9p0({+?-mnZOpI=P~j{pE8VuN>LV;ixtwTt!GlWy$Em2fji z28LGpSquaw*Hu((70NW9_oDfH+14v2;#xlcL8V~&9JU8!*&u**BIb!Lu?SKT;3eWG z04#T&rYx!HCh35nq7F65Zs4m!B}I5U?Z1cLzNyM3US!6cW7yXJjlRF#!NlSD!RA|u zwl9u8e3eez>;)nA7y>-jt~f#4M4X%jt)ED%#AzD`41Gs*bn)cw$m!w*9>G%{!Be|= zdQcDPVLeicEXfY;vej0k%aR_w60XV<wmC~mRhbR8M|#iuLMz(~_bSw;ZvS50adK0ns;of{-`%?R&-PRUyx&1LwVJ-; zyrzhC@*r_AQ?=R{VTYGgXJ30v$QoYVT}{`j>R05O9e$I@G*pZw^<&iv``>wKo-72W z$vmXN|7P7%u!?!UchU=*cFC?do(f;X!fa78xI>v2=i4qqo-WKm-q$?^)8%3)ThPmf zm0b*Hxl?eAVirhcC=G?|VlZnQf7Z$tw?!pEe?1NF<^=#>NUsq|WVm+^EJ;`8*^b?7 z+vjwsJ2T!}WEE=WwNXs-;-aM?vCkEp87~T(VGf@+oSAgMlQU1T!=Q!XWojkAWpM`| zM8-?h0+ZO;#S{N{6aD0u88>af_}NTn4l3nn&em__H^@-6k!k2ZIbUA<`kgz;lN-sC zH{Yx$PrJ#}K4&`=_t{gQpLqS1$;AT?Upqw^v)SoXx|d-%dx3)Wk*8ERIAy@#`9j5+ zp%xr^!2yS8K;e}+$^<_&$L!07Nj-(xCKZ*=i-JVUeGDDpMP6JeQjy<;_e-Ex6-%Z= zqIQS_^Y+Zo7!EZt81y_IFZcRD(>5x@O65tEVIXt7RjK!&zO*Wg$nTFxbpG)U;nX+5 z?_0~{evtIWufJT^lCGA#tHrLLuWKW&HiGD@b#2tuMggtGkJPoJu67j3FI|1(+8YgR zZ$sNBV7wWNCZkYd?+=sVH~DC*2(U zal}3Tsy}GTJ-z3o#_;1`A9IH@*F#X{;)&14*Phvk9jV2RG!mnC62~?Y$G*QcS zr)u$2_hQ3$Vuv)D60DxW}c)=Zbp|-1UL2CwZJ|eKSp`BH^(LcD-&kw-q-ja`=i<4WK zF@U$@(Frwh`#^Gn1b!we;FAnJz_7aer4xS+7|s&_lh(gg;uKcm-N09en$iQ_kUk54 zb#|uW7$(m+tYC2y@`i0OO!3}8i_K?j=D;5q_XD-3J&T##zY2XVO3}6)W?W*J;2B1q zC*{2mq%g=~B`*TMpcep#px%!HClQFh3t?Edu@A!I{$I*j_8iDz3gRvRIA2L>kmI%P z-XMo+-Mv9ZYu&v;4%WK+!}Rnmi|1ZscxBLy41f7-Ju>D-#+HLkB`m>_?goD#?a#j% S@iCO!4gNsdpATUOpZo;@TlK~O delta 669 zcmY*X&ubGw6rP#=nWRZ*v~&v%EFKCAMenJ2Xe}igrb;VFJjm*f+r;f|nB5vEk$UK% z(38xeLKS+76+uw+e-QDIN<~@pDB?}<=E*mkHsE9D&HM4a$M-GMpL&!pJuepX2<+9Z zxb+i1E?HZvknJH4c{o7pxQVfhNkE!}AV%7XL`!38A0zY<&d{0~K}*O}wvngy33T9` zbaaobAV>QpQwOJGK1*%~Zs>2gKBIR}P}zl%sN%5dV0wz_E#ld{)P|_R%x_f0-EdGBI?sczJRD#!}6`<|b=3d()h?8=wiI{H(nYMfY2*W~XJZ z4aI0A*U`D8o@yQ53X@drz%Z92Q)6Ly`D%S}B~{?}I0`qRD0ay-&WS^EL6M)3S?Lt#IgS-ZtK#a@f4RDdxqJWc9Ln_Nj;OaO=Av|36-0;M>1}6v}Ek0&eX~D{VQW! znjQ4N$nL_sFj3?lpR2y@|7?X{ADOp8dkl-h>Ta8s-%{Yu?~x5RBfWql#%3bbc*Vu{Y71 z%=$5TQ=AIxrclHV>IXh<99mDe2xHx#i2bTsx#y8Q_vx z3nxrEXpJ{QU5;*il+`dsj*uP|3zQ#LD%VJH+N?a?!~Rh$leQuDGGMJEqLrgL*x<{|-!qtzAn%9coXsQ``ghh&F3~UnaRcu(%Cp=fU5}U9bYq?N5 zyKTlkYkxb>zLaNQ%yTT~ITm9b6Z}d{!tv1sjhUuOi*bJ>q)9rj*Aub_ew~m{%urEc z0Ak1=A7Htu1tBto5}dm#omI`XJ6dX+8eLV7Hq}HZUygcb}+w=%=OAfP|#CtI#F0qw_Nbd|sJ7G^! zLE>Si2}SrjdIV2U45>X-z}@(5m`o}m0$fe7b|A6&wi&E;prT;73^%N*2sVF=coA_9abB+KB#MY0 zNwkhA$w{!Mm?gC>M@0Z^g}-72eo<)#Wl<#wz{5ptz9Be+P40-XOn+vEnB3I=oq&LFVCK#h1thxLA_e`v>eXAMpla0Wptw z6S0UGVhBaELxg*3#3f%NzeNKMG^BHjqOYr8@Vew_rC(Jmj*q@BSNIZ&h#9Hv89r2cQWm_R3*G}2*g!?v@H?h7u%Ztg!aHhQ0 zCXaPPXSXm~?k2+H?+>zW!WDli=UzoC;s$JSoFc1O99(yNK`z5P71#LxFw3X#O63{N z9VW5+sS6HNm5|Tjg{ledZHz^%f~ER5R zS`O8SJJP9!agO7Nq;Hz$*jQiQX(OlM?>oyAKgXS2z;5*B-9~HQvB{;5H5+pN7W|judS`h(qy!y zAav-uiAgPVn;ehI%W@JXjXqEnUe|O>O{10GfPsD1EL;VefqwWFktc-8Q&W%42E_N^ z(!MRKbmjwdDkDVgKcvDc;7W6{wwwvI>?an(laYFOWq+Gm!jXt%Tl!w>k#Q11z3xU;M_PPa}?_LdC2uPbH6Yb=%`T-4!AdHh(t74VAA>)8vv?;HyQN5>mm?w!c2$sJ_+^*91p5m`cL_muRl4rJ zlJE^2P8dY1ntc$l7gZ=i820oHvJ)@>x!uW$_jQ27;Jc`QFhCn%e|HVv!8Cqpx_cK< zPhhg|0_jOBJ&YpWnDXr>UqJFrAs>197>2j6JX!LVz(Lav*weE~iywvTPa%F&8t=&> dHY+JvWmC8j6P?HVK0*Dhb>nU#&G(jY{{>j}?u!5b delta 2407 zcmZvde@s(X6vum8`-&Y@u~3T2uZci`VvI6zDhz5-f&vOCFr}8hM}1J*df%&xaS}J3 zMqS)IW^VJx{ITFBx@5k@WD9OOrz^N`O^2vx96OD z&$&O}PuIz+E5x!lF3zlD&*dqySUBYiOX?7E7OqZ;!0sV^U3#5hY}4Er!YA-U`NaLU zZi7zO!`|rAG;Bar42v4NKWRXeRY>6Nt90G0QP0z+x~4=vd6llo#t*O6IYy|a8X9U9 z1A=O*onBE>CM z!O#*oF=862S1cnrGOP)^kQp3ACGDT-={(3vDM*sAQ&Xs=P4LN*%K50k%A{S;o>E8} zVONSc8Pn2C42#1d)QKI@VbEphz_RI5#BvOYm1M6h`Z@%8VTh-1!%?%nE?957r1EU*R^`@*EvH}{i`ngRQJ0E6c+sS4a+L8b(vYq5DWq-DXaO;_` zKL^e`Y;ZB>0eKHjJNo0dV1sRlRE8=?E|fMT!p+>=0<9};p*ExYBgDsuJ&5-aA0l=j zcEgmsbPiAk!I!r^a}7#ugxRPTA+%guQSC&0qK$4HN<_0#n%`+4>mW8QS-CJKYY^E0 zkH_`0fu3?F!};-Lq!khh8_8D?EbLGHhP7ChRJ%|NcL+hbfEOjUOFBIvD*DCXVd#40 z6gdW!6ROD(*g7GD>BAGgCf%^SXjJ=g%$Se(4sjCkJ>mr72SkuTjpKy?Zk|pLk864q z9ayMp@k)~D4+^|TrV(E|ebHF4K6*qO;X#y$?Mn8_8XZYSy0`j*FV-N*JpL> zSaa?oo~((K5@#i7G7PMZy!9;!`LC8`;ScVI*FIFbxU znm3YEsPUwli&*>=_DbWm)miUpVsK2%Or&sIabvmfc;VT;o_U=@lna==oIMH?bdsqt0nV^BH>y#srMMah2F zKikf3OtT8H8V!S(*V$TToW?@uDsroVOmJZCU=DRNI)p2KIUaV jo%Dt>vm=w_r;x%v-T$$+bwl7D+V4Me7Are~h5CO1A1962 diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index ac7f5f776ea67087f2bc03f88d8427ace09a6485..36d473578988d6f649b0bf48f0a6e66231828bc8 100644 GIT binary patch delta 217 zcmaDacuz=UIWI340}!yfmSozpF)%y^abQ3K%J{6ryiwx^)5Hg?n%b$%SyC|h6rD9} z%a|D$Rs%5vq>DsJr|1SVXzFd&VxGoiP$gPinpB)zlvz-cnV+YdT2YW$l$xS{O9WXs z9xCjoDKq&n>sF>B>B*&R0bFW8BN>6X*l_cCHUTEh8!Q|RE*Cfz7N}lPvbo?Kd4VPB XB1_a2mZ%S!qWnw^+#pz_0@MHi40bo` delta 110 zcmca7^j=V7IWI340}wo0U7Y!lm4V?ghyw%SP{!wfOdBDKWDLq+)ZR_N7Y`$EoK(&lOTr9C!m0f^I;DfpdKT`uY2o@;=#Q|gk7n=Y8 diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 34d549ccd23601a64e6c694f711f8537e9107b41..ac18520979072faaa54cb0d7510d4ec3791289e8 100644 GIT binary patch delta 6159 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 diff --git a/core/admin.py b/core/admin.py index ba958ef..656f462 100644 --- a/core/admin.py +++ b/core/admin.py @@ -4,6 +4,8 @@ from django.shortcuts import render from django.http import HttpResponseRedirect from django.contrib import messages from django.utils.translation import gettext_lazy as _ +from django.utils import timezone +from django.utils.html import format_html from .models import Profile, Truck, Shipment, Bid, Message, WhatsAppConfig, Country, City, TruckType, AppSetting, Banner, HomeSection from .whatsapp import send_whatsapp_message @@ -25,9 +27,24 @@ class TruckTypeAdmin(admin.ModelAdmin): @admin.register(Profile) class ProfileAdmin(admin.ModelAdmin): - list_display = ('user', 'role', 'country_code', 'phone_number', 'subscription_plan', 'is_subscription_active') + list_display = ('user', 'role', 'phone_number', 'subscription_plan', 'subscription_expiry', 'subscription_status') list_filter = ('role', 'subscription_plan', 'is_subscription_active') search_fields = ('user__username', 'phone_number') + + def subscription_status(self, obj): + if obj.subscription_plan == 'NONE': + return format_html('{}', _('No Plan')) + + if obj.is_expired(): + return format_html('{}', _('Expired')) + + days = obj.days_until_expiry() + if days <= 7: + return format_html('{} ({} {})', _('Expiring soon'), days, _('days')) + + return format_html('{}', _('Active')) + + subscription_status.short_description = _('Subscription Status') @admin.register(Truck) class TruckAdmin(admin.ModelAdmin): @@ -117,4 +134,4 @@ class HomeSectionAdmin(admin.ModelAdmin): list_display = ('title', 'section_type', 'order', 'is_active') list_editable = ('order', 'is_active') list_filter = ('section_type', 'is_active', 'background_color') - search_fields = ('title', 'title_ar', 'subtitle', 'subtitle_ar', 'content', 'content_ar') \ No newline at end of file + search_fields = ('title', 'title_ar', 'subtitle', 'subtitle_ar', 'content', 'content_ar') diff --git a/core/management/commands/check_subscriptions.py b/core/management/commands/check_subscriptions.py new file mode 100644 index 0000000..146d0ec --- /dev/null +++ b/core/management/commands/check_subscriptions.py @@ -0,0 +1,57 @@ +from django.core.management.base import BaseCommand +from django.utils import timezone +from core.models import Profile +from core.whatsapp import send_whatsapp_message +from django.utils.translation import gettext as _ +from datetime import timedelta + +class Command(BaseCommand): + help = 'Checks for expiring subscriptions and sends reminders via WhatsApp' + + def handle(self, *args, **options): + today = timezone.now().date() + + # Reminders for 7 days, 3 days, and 1 day before expiry + reminder_days = [7, 3, 1] + + for days in reminder_days: + expiry_date = today + timedelta(days=days) + expiring_profiles = Profile.objects.filter( + subscription_expiry=expiry_date, + subscription_plan__in=['MONTHLY', 'ANNUAL'] + ) + + for profile in expiring_profiles: + full_phone = profile.full_phone_number + if full_phone: + message = _( + "Hello %(user)s, your MASAR CARGO subscription (%(plan)s) will expire in %(days)s days on %(date)s. " + "Please renew to avoid account suspension." + ) % { + 'user': profile.user.username, + 'plan': profile.get_subscription_plan_display(), + 'days': days, + 'date': profile.subscription_expiry.strftime('%d/%m/%Y') + } + + self.stdout.write(f"Sending reminder to {profile.user.username} ({full_phone}) - {days} days left") + send_whatsapp_message(full_phone, message) + + # Also check for profiles that expired TODAY and send a notification + expired_today = Profile.objects.filter( + subscription_expiry=today, + subscription_plan__in=['MONTHLY', 'ANNUAL'] + ) + for profile in expired_today: + full_phone = profile.full_phone_number + if full_phone: + message = _( + "Hello %(user)s, your MASAR CARGO subscription has EXPIRED today. " + "Your account is now suspended. Please contact support to renew." + ) % { + 'user': profile.user.username + } + self.stdout.write(f"Sending expiry notice to {profile.user.username} ({full_phone})") + send_whatsapp_message(full_phone, message) + + self.stdout.write(self.style.SUCCESS('Successfully checked subscriptions')) diff --git a/core/middleware.py b/core/middleware.py index 724145c..d5ee3ba 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -1,4 +1,8 @@ from django.utils.translation import get_language +from django.shortcuts import redirect +from django.urls import reverse +from django.contrib import messages +from django.utils.translation import gettext as _ import logging logger = logging.getLogger(__name__) @@ -10,5 +14,43 @@ class LanguageDebugMiddleware: def __call__(self, request): response = self.get_response(request) # Log the current language for debugging - print(f"DEBUG: Path: {request.path}, Lang: {get_language()}, Cookie: {request.COOKIES.get('django_language')}") + # print(f"DEBUG: Path: {request.path}, Lang: {get_language()}, Cookie: {request.COOKIES.get('django_language')}") return response + +class SubscriptionMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + # Whitelisted paths that are always accessible + whitelisted_paths = [ + reverse('logout'), + reverse('login'), + reverse('register'), + reverse('subscription_expired'), + reverse('home'), + '/admin/', + '/static/', + '/media/', + '/i18n/', + ] + + # Check if the current path starts with any whitelisted path + is_whitelisted = any(request.path.startswith(path) for path in whitelisted_paths) + + if request.user.is_authenticated and not request.user.is_superuser: + try: + profile = request.user.profile + # If they are an admin role (not superuser but ADMIN role in profile), maybe don't suspend? + # Usually admins are exempted. + if profile.role == 'ADMIN': + return self.get_response(request) + + if profile.is_expired() and not is_whitelisted: + # Only redirect if they are not already on a whitelisted page + return redirect('subscription_expired') + except Exception as e: + logger.error(f"SubscriptionMiddleware error: {e}") + + response = self.get_response(request) + return response \ No newline at end of file diff --git a/core/models.py b/core/models.py index 062934d..cf5ba9c 100644 --- a/core/models.py +++ b/core/models.py @@ -61,6 +61,18 @@ class Profile(models.Model): subscription_plan = models.CharField(max_length=20, choices=SUBSCRIPTION_CHOICES, default='NONE') subscription_expiry = models.DateField(null=True, blank=True) is_subscription_active = models.BooleanField(default=False) + def is_expired(self): + if self.subscription_plan == "NONE": + return True + if not self.subscription_expiry: + return True + return self.subscription_expiry < timezone.now().date() + + def days_until_expiry(self): + if not self.subscription_expiry: + return 0 + delta = self.subscription_expiry - timezone.now().date() + return delta.days country_code = models.CharField(max_length=5, blank=True, default="966") phone_number = models.CharField(max_length=20, unique=True, null=True) # Changed to unique and nullable for migration safety diff --git a/core/templates/core/subscription_expired.html b/core/templates/core/subscription_expired.html new file mode 100644 index 0000000..01b2bd5 --- /dev/null +++ b/core/templates/core/subscription_expired.html @@ -0,0 +1,53 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} + +{% block content %} + +{% endblock %} diff --git a/core/templates/registration/register.html b/core/templates/registration/register.html index 8b142ec..e9596cf 100644 --- a/core/templates/registration/register.html +++ b/core/templates/registration/register.html @@ -104,44 +104,70 @@ {% if subscription_enabled %} {% endif %} diff --git a/core/urls.py b/core/urls.py index cc689f2..9f88dc6 100644 --- a/core/urls.py +++ b/core/urls.py @@ -22,4 +22,5 @@ urlpatterns = [ path("bid//reject/", views.reject_bid, name="reject_bid"), path("privacy-policy/", views.privacy_policy, name="privacy_policy"), path("terms-of-service/", views.terms_of_service, name="terms_of_service"), + path("subscription-expired/", views.subscription_expired, name="subscription_expired"), ] diff --git a/core/views.py b/core/views.py index d6d8d13..a5102b8 100644 --- a/core/views.py +++ b/core/views.py @@ -1,3 +1,4 @@ +from datetime import timedelta from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib.auth import login, authenticate, logout @@ -27,15 +28,16 @@ def register(request): app_settings = AppSetting.objects.first() subscription_enabled = app_settings.subscription_enabled if app_settings else False - # Fees for JS - using float for cleaner JSON and explicit formatting + # Simplified fees dictionary for JS + # Ensuring keys are exactly as they appear in Profile.ROLE_CHOICES fees = { 'SHIPPER': { - 'MONTHLY': "{:.2f}".format(app_settings.shipper_monthly_fee) if app_settings else "0.00", - 'ANNUAL': "{:.2f}".format(app_settings.shipper_annual_fee) if app_settings else "0.00", + 'MONTHLY': str(app_settings.shipper_monthly_fee) if app_settings else "0.00", + 'ANNUAL': str(app_settings.shipper_annual_fee) if app_settings else "0.00", }, 'TRUCK_OWNER': { - 'MONTHLY': "{:.2f}".format(app_settings.truck_owner_monthly_fee) if app_settings else "0.00", - 'ANNUAL': "{:.2f}".format(app_settings.truck_owner_annual_fee) if app_settings else "0.00", + 'MONTHLY': str(app_settings.truck_owner_monthly_fee) if app_settings else "0.00", + 'ANNUAL': str(app_settings.truck_owner_annual_fee) if app_settings else "0.00", } } @@ -103,6 +105,10 @@ def verify_otp_registration(request): profile.subscription_plan = registration_data.get('subscription_plan', 'NONE') if profile.subscription_plan != 'NONE': profile.is_subscription_active = True + if profile.subscription_plan == 'MONTHLY': + profile.subscription_expiry = timezone.now().date() + timedelta(days=30) + elif profile.subscription_plan == 'ANNUAL': + profile.subscription_expiry = timezone.now().date() + timedelta(days=365) profile.save() login(request, user) @@ -427,3 +433,15 @@ def terms_of_service(request): } } return render(request, 'core/article_detail.html', context) +@login_required +def subscription_expired(request): + profile = request.user.profile + if not profile.is_expired(): + return redirect('dashboard') + + app_settings = AppSetting.objects.first() + return render(request, 'core/subscription_expired.html', { + 'profile': profile, + 'app_settings': app_settings + }) + diff --git a/locale/ar/LC_MESSAGES/django.mo b/locale/ar/LC_MESSAGES/django.mo index 5549a4aa36b633ff133ee1e74e859f2d1a37cf33..300053114d1ed11980640de453c5ccf1d9745669 100644 GIT binary patch delta 8042 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