From 3811cb2c8d1b789ee404fa223d670409692abb4a Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 14 Nov 2025 12:22:51 +0000 Subject: [PATCH] UNLIMITED DATA PLUGS --- config/__pycache__/settings.cpython-311.pyc | Bin 4210 -> 4616 bytes config/__pycache__/urls.cpython-311.pyc | Bin 1143 -> 1229 bytes config/settings.py | 13 ++ config/urls.py | 1 + core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 2541 bytes .../context_processors.cpython-311.pyc | Bin 0 -> 393 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 7177 bytes .../__pycache__/paystack_urls.cpython-311.pyc | Bin 0 -> 851 bytes .../paystack_views.cpython-311.pyc | Bin 0 -> 2221 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 1046 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 7695 bytes core/admin.py | 31 ++- core/context_processors.py | 4 + core/migrations/0001_initial.py | 39 ++++ core/migrations/0002_cart_cartitem.py | 35 ++++ core/migrations/0003_order_orderitem.py | 35 ++++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 2147 bytes .../0002_cart_cartitem.cpython-311.pyc | Bin 0 -> 2190 bytes .../0003_order_orderitem.cpython-311.pyc | Bin 0 -> 2117 bytes core/models.py | 81 +++++++- core/paystack_urls.py | 10 + core/paystack_views.py | 37 ++++ core/templates/base.html | 69 ++++++- core/templates/core/cart_detail.html | 39 ++++ core/templates/core/checkout_complete.html | 10 + core/templates/core/index.html | 187 ++++-------------- core/templates/core/payment_failure.html | 8 + core/templates/core/payment_success.html | 9 + core/templates/registration/signup.html | 22 +++ core/templatetags/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 170 bytes .../__pycache__/cart_tags.cpython-311.pyc | Bin 0 -> 473 bytes core/templatetags/cart_tags.py | 7 + core/urls.py | 16 +- core/views.py | 149 ++++++++++++-- requirements.txt | 7 +- static/css/custom.css | 107 ++++++++++ 37 files changed, 731 insertions(+), 185 deletions(-) create mode 100644 core/__pycache__/context_processors.cpython-311.pyc create mode 100644 core/__pycache__/paystack_urls.cpython-311.pyc create mode 100644 core/__pycache__/paystack_views.cpython-311.pyc create mode 100644 core/context_processors.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/0002_cart_cartitem.py create mode 100644 core/migrations/0003_order_orderitem.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0002_cart_cartitem.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0003_order_orderitem.cpython-311.pyc create mode 100644 core/paystack_urls.py create mode 100644 core/paystack_views.py create mode 100644 core/templates/core/cart_detail.html create mode 100644 core/templates/core/checkout_complete.html create mode 100644 core/templates/core/payment_failure.html create mode 100644 core/templates/core/payment_success.html create mode 100644 core/templates/registration/signup.html create mode 100644 core/templatetags/__init__.py create mode 100644 core/templatetags/__pycache__/__init__.cpython-311.pyc create mode 100644 core/templatetags/__pycache__/cart_tags.cpython-311.pyc create mode 100644 core/templatetags/cart_tags.py create mode 100644 static/css/custom.css diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index dadfaa7db630a06a9bc0da9edbc375273aefa0ed..2a77dfa4ecab085afb590cf825d2ffeb3cf88ba0 100644 GIT binary patch delta 782 zcmZXR%TE(g6vprEw3O+SLTAdO(BV-EB2`g*pfQ1ALK>Pv`dDx`B?xvWmha@VPZ5jac`pQ`dBox@=uuHLRZE+B@l(1e7|$bEmy$B3C?lbRZ-s6S?m6)Aw$u2_}g0CD1y{0-DWZ+x}ccCK;JVam+hMElFV3_bQ zLLy}Z(L+oIVU!HP7@c_`MkyR(6~lPbS;k}-CI~kIlQ4Deh`6zbV|cpDgiU4!d`2CQ zHfG_{|JQ>#xJ);kZ@N`XqL73ube4pxU0&;QLB(Y!8HMX)3~rDZc!@wY4vS=>#KO(G z7a=l9vs-pX<83>Z9b^jbkT@)nX<%|__O3FAQHy8ZFu2$F)YEL3LD*3$<*K}^?ALUq z_|P2KT;9s%m&J8?v#^p*iSoL%^^cj8#H^IJ&3f>3&HVNgU9VP5CbO2Q2e>Y@&gD$kqiR{Lm@c&frM;Ho{(!O5 z7-FA|)uY9#R!SDD6}_~l%ez{&SgO^kS}j>rG~GC2zxE{PCu<9>hNp&c!uaX^c266z zttf>VI(tt}%I8y=wVcVOH`Y=axsXkp{U>QwT1{moF>iauXWy|$rb{tZ?N{rge+55a z`yCcK#X-R0QABvY{ZefUi*L0r{L*)Rsf`9YeC(q6BQ(+J<2vEE<#uv(7m79tSw7?s sAFv%R+D84B)8K-MxN9 delta 369 zcmX|*J4*vm5QXpD8#mcqca!Mmo$TiQFurZA`~f!UZYNAwV0Ne=AVwdM|o##>K1?eJg@KRsJ%?q6nZp&>e*yP*} zrc1elJG_j$yn?JpI(tW5Al@l^WDpgl=)^mWy#g?yXk+oLy(_l-=e*-x>qCI5_+I`H zhB?=I@(R+!2TzS3Kr*&Mee#Eq>04r!Xf+}=(;=lk0l<|4VMD=nZCWchj6jmU~lES`>k%3_~ zL7p3YIBvuxeBqnFaPmW?;=lkql<~P{qIx-3I#U!!3VSevCdbAL+n6TL bVxGfTGTEFZN8kf1Ge1)UHwYH-0kr@CF=i72 diff --git a/config/settings.py b/config/settings.py index 001b8c8..6e91bc6 100644 --- a/config/settings.py +++ b/config/settings.py @@ -54,8 +54,20 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'core', + 'django_paystack', ] +PAYSTACK_PUBLIC_KEY = os.getenv("PAYSTACK_PUBLIC_KEY", "") +PAYSTACK_SECRET_KEY = os.getenv("PAYSTACK_SECRET_KEY", "") +PAYSTACK_SETTINGS = { + 'BUTTON_ID': 'paystack-button', + 'CURRENCY': 'NGN', + 'BUTTON_CLASS': 'btn btn-primary', +} + +LOGIN_URL = 'login' +LOGIN_REDIRECT_URL = 'index' + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -78,6 +90,7 @@ TEMPLATES = [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'core.context_processors.cart', ], }, }, diff --git a/config/urls.py b/config/urls.py index 5093d47..8ccc2b5 100644 --- a/config/urls.py +++ b/config/urls.py @@ -20,4 +20,5 @@ from django.urls import include, path urlpatterns = [ path("admin/", admin.site.urls), path("", include("core.urls")), + path("", include("core.paystack_urls")), ] diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index cd6f855b12f4883b1ba9de01c54245c53aacd714..4f7d3cf5d48da6daf7c0ae848fcdb5e0229011cd 100644 GIT binary patch literal 2541 zcmb7Fy>Ht_6u;w#M9cc}haB5!Y`IR05`nELLyMw75u`?t#x2lx;X=^5i^GJJ)Fai@ zn}+-W+Ol?XQRJ`aQgoC74Z=+ZI%TurOzG11j-qT!Y~hmPk9WNH`Frn9zm`e`0@t5^ z*ZitM$X_@~uiSWW@dqX3HDQF&fP~bclrW9yffnkH9vY4jnvNOfoLrc9@}cEelxT$0 z=X0qct{;PiH}E8vM)n9ZUJ+)3l=6??k9ClNC%KN{^(LJVXMW0}kzp|h7V|f@SX5D1 zpfd+LrPS5+D4g7qfX!*$)%3qopz@iKt9ZU>hpj_zmW@!?)^^V zSv%y7?m@Q|cN_d?-t5M0(RkSwFQSgu0YQmWxqgkxA!aFvXGAY*a2_jt6t(|dwua0n0yAfm%idP&hkar0}wXl5@r}xCvFrF&(?7?d}!)&f=b?L}E;&%M@R zd$-wc#lk-jI((No)zFbJVIuHi>TItnZ zZS<`Zx;CV1XPQQL&&VwF9}buAp5wwhgmax}>l1m4XjNq;e?nJ>boETr(aJ3J?+=%@ z&vD@$0$KsDWBGzc>*#tK`acY7pP=hHx*o4iTpA~|Hl#HSd=s^&p>GbWb=2NO?M-yK uGLg3?_AV{ssLzKh_P@F`PbipJ!7L$~S?Je>3tN~a1Pp*KZ_Uc9?f(xd7aWEF literal 212 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 K+NP5l@c#i%59Hwh delta 151 zcmeCQxX75koR^o20SH!P{K~Kd(vLwL7+``jJ_`XE(-~42QW$d>av7r-85vTTf*CZK zUxE~9GTvg#%}+_qDfZK3y2Y82m6(^Fua}Zk#0->N$?zGZX0jZ!f!Hq&8=$(V8V81nxx$yac0)qm_rW! z1tQeHpjzxz@Z{gHfjxvh37&cjvqRCgn#o|6j}+Eqp@s)@rSzH*>VOkE*3BMOei#>07dUf#VEk9%cLr8d2#|-#Qi2 zvX9&Ed+bCEDsXn0CruI9HWR|!5t6^)?2y4do7K&@oFLi>LUDVR%LeP|)kCZ21f1P~ zCYizA6q4L+_pJ^yZKWkO0)ZOCY!loWk~eBY81A_SV}iXfXi(c5Jg|Lu+ls7OSGo>Y zY#=bp*}H%VDHL1f`nlQlU1kOcfhPl#ePDJVeQuuk{8;oY8x$pki^&As5w-s5b#BM8 z+yiRWc?oFpu*Gd~BlPp>uZ?i`?RdKtZMQ~f21yj(SrIhvb+4L_3-rkHI(|*ja@A_Q zo@7MV=MuCCZm2Zk>g)5Ia_SF^3vdNKqh#C@7l@Ztp$HC?xjqdXR_FKNsG1aCz)f|G z@gMXkTwW9OBwSvTWdPu(8D!)~ufqJ+_g3`mb@;{(2^|v}5jr6`Re1Qd6+M0#?(T=A iF(!?OG?GFk+-#}EK89pCCc}sfr&&_gMg{fERsRE56Y2*5 literal 0 HcmV?d00001 diff --git a/core/__pycache__/paystack_views.cpython-311.pyc b/core/__pycache__/paystack_views.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd97d1a8a97eccb4cd1590a030a690c769b3e41a GIT binary patch literal 2221 zcmb7E&1)M+6rWv-C?pR)BKa`o> zxHbxyVrVHolw!ys6nrRdi~R@m*jtZX8G*%uAy9hAO~LIYr@k3^Whreb9cJIWeP8qO ze(!ys&u0;g-U-$6W_knF#z?L)9t+LP->eE| z_71A3&$vlNjuc8L*~67U`U41pdapUaL%gNGJld)Olu zq84g!PcinS9kAXVcs!E*bgV;YC{pr7dcqAyYRL^H)|hfj+Dq&}4tL?}k4Fix;HMU2 zduW$fhF;3U4Rr77BkWI{=>PLPi56}M3~`@fzM;lZK$?sBJv@3w32k2czo*>J$I*t@ zQAA0|pFYu=$jcJC< z6@wDpCv=SeBrQNBnCP}m`Rh`i5_*c064xsxu?ZOSTvw7&iesEGKgtZw8DRrpPK(?c zi)b8A8Xqq%&nH zX#Ayn(F+igUyA(-WF7skj&EK+P|N#jd3*A^bD>&ptJRKLUB7XdD{g*#kkj^aS~p+X z{Ipv-(<#k#^TqDj*LoS0zwjI-^U5EbtiRvOq2lSS?BlU-#@Yp~Q_y-yNoEE45+A0E zo%GZ}TH8-+-HAyEwDnPU;+3sSor#(5>8Y)I5H7>RWwH=118xJ719s!XFXlg=Z{t!2 zmkw}wAD7#Bx`U@fJbkF13QHH;>TE}y4e{&|TnQP2GKC-h*rEZv3Jf%WAzcfa;|`Sm z?x6euV)FdjRQ!^ntt{!59Z*tT_8q$-Q63AHbFeTTOQRallUF`Ar!yce!^e33*U_O` z{QC88Mvie1-D!7=s1_KfEpQ@;iDH$mmH;sUhypl|VLSwN#sz`60- z%`C^s-DQhu%&NP(tpR5M7>TK^EE8ALwPPr7#WBj40^45I;?`LeA=b1{`Ym-J*Ool0 z4N!VbTgz*@YihdV0T{1oyefG;xjqz{!ukz90rrv7H;I({D>|(+#0y6u`@;| zgo%uanv2w9o#DAllvh@C{zT$aYq6>a{&LkM0013|i&37FNO3%FdL)`2~Bu*;s{~~=IA_q2km%arq98K&OAiabnNl(!m z;XlnT(!$Z%MOVYo*+mz_f1h3Sb~rkpO5;7bAYJMqAkPy>E^M6d3uYwgogNwx1<4Z~ XX{skDrCSmdDNt^H_9a=GtB~VI{7#Kp^m?Bjw2q6_!4XRp}s*9J%_>ROSwxi3Lk946M zW41D8qN?Z*=+Cev56EjLHY7Ht-Z@DtRayG_-qYRl_@4KdfFXO@`uYAR`AD7(W70-TT3IZ^n%mmT! z3RC5}{ISfLUPP)F!T$_{EP`{E{5Ioi=}#|c88wav~v{$%_0pyC8Rqwapi4q2lT(q_A(h|(t2#e#)w z%#E2_uFp1GG{B+5YA)V3sg@}Yt(jE3n9kJD;In~2lNth_=-d2@idVE(E~du7hbT?O zGoZk`R5^0VaXZ^fW<9HxdCKZ!qEu+424BgKml5i>zoR=2q5G$1Bk+hB?(@BdCOIKa z$O38}2lTMrvK?YN0p&C=S!tc#Yg-=QR+DVgS-x0YPP@qa+ngjx*Jvdg)vnP&^#AQP zj}-mv_4)Mq$BVfLmSb2>VEGy}jy>$Y>CH#58pCP=t9|TIx8AKsOE2TnMpD{{VKafv zJ~P*S(t97ldJO9ctY=uHuk=3ud>lbFhH3)Up+uu+MX(mbS^{f>9~YygO7x-{!EOw@ h3G5Ewg^RuD`Kt(aV%SMw=XOe;kk1V9(HVX!zX3Uu9hLw9 delta 278 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 RN=s4Nr43b;!8~1~{{^~Mks|;A literal 1364 zcmZ`(&1)M+6ra_{dS%J>I!@|TuH%hUh$T{~CE!96oTjQ1oHkCdQ%ZHHPf-Q6#oP5Eq{V!1Q7#*Ku^61OfEk4%~~s}guETzy!YnKoA>c! z|CpMZL_mL+PRlpL2>m0JfI>sz+l$SscSs^=|k4VB6wxYJ2mwh>6 zP1s6a0Xh0B%Et_88>QrbMYzqB#N#E*3Oks3CUHQODRvA@K~k7$;|6iCE(zZu#bJlk z(8(RJK1WUTVt6pV9fEo^GQIeJgpqaJXHRNMhV;}glZK3tBN^en5Q~o7QAND#(>piA zMx+^HvEUS;@pC3}9#TaPq;a2SxGAxj;0&RP639l>kPjiNqhJ8&1AY_l`WecBvKL`< zK_mp-GGfihA=Gg+ss)BI*;w21yRNY^)~upqm`+Labc(T~)l62_EK-D_XmFxoXUC+( zv9ZH61GA!Od6`C{=(;tt(Wqy1VsquT>;5P-9=l@|nMF#b-iU4DnzqgA7Upvgw;tSE z&pj%9yS|;<+IaYA=m{Nfc9(G8LP76tKq2MQk+Efgahls~zxB^Y15iq2p ze1hU%s@P+67IZo#*hj*3LMINxyUY_2H9@DJWy{3I+3Mq}!>U=li}flaG+QI|spl4T zob~hDF>%eyxOE;8-gFVJ3n+TO18bo->ila}J65%hy4+QlU#ly}>PkmVchz)jqo<`_ z{(P+6Xlpn2)BEXH3$5?^ljojY`z`Y<)5;BG^zo;?`Q?6crISo|lj(kPxtsi=lf2$d zUhiF8=v}zfU%GryZ>QE?g*r>Q?ozJ5xODJS`|4MJebZUI)m^+bn2pWI0|cyf_w9L< zIJbYby>RVisS{uA##h^s)e}e(07x&wM(2TX`E06G0Q9V<>X{|ul?i3g0?%(HPYlgf z1`zb;;Me{qqL;+gPk@~&H*I26ExbmrfGYk1-VfII+y+4vAd=f;QQ;r17@eG!qlBI`F;j+PX5PV^o4|Z9fmxcsEOX2me;$%-`MZo-5#QRH z9rxz^9?s%iZ{;+@zY-voHATN?CIM-(9O>M<^T-{oYbqLzkK$5^l@FMVUmD&~iH0p2 zF*@GahXXt4-CmXhOAQWmi!(rLK{@KqHv!C|%8_@ew7mVuRUZNjDzI3dS_v?O;Zi=ZkU% z!AGMFpWtZ63)gxy-7rj2@EyQO%+O^x=EMe8O``F6IIE&<m7ElnC$S=L|JWK zhaImnerc?7S20K3E!eVAcbh!4y>4RNqi&>?4MQV}?rGEO5c{^;CY~CvLZWI@{|YK6 zj9ZG{G^mK{G|H2O3ywxA*S9Ly*T1B3qeDiQ-mmC2goWN*E8aEQq}Z)@b-P<64@m=p zG>Z=m^Pbg#T`Mvgi*GN>;_B+^C-NxuLTArpmmnAH1X;hqEwMlTy_}idzcCC7^Wpuk zhfx&E{uFnk%Y)Rpm#GCOeevM+_dE+?LjK-Ncn?BSIR%z zdOq(+)xK19rK*#>@vbZQeD1}zBW?DjO;_4b@(Zp}%4K1iK)GM5f6 zxS2~w-@W+M$!zp98*XO9Nq#d(Wu28PPZr&kD@WVTO1ZyMc2~+yvNA|5IoZnxyKeR} zWSXt^vsE|iWjc1ih-|r^ExTE7#5rfFaPYmmR5)7vU2;pe+|s67syV7PL}*XADZ*>{ zmiUUx6E4A$x-AYQ>}Ou{cQYTRl2=RxTrYSZ$bw%{W(i<4 zHum$P=XLIt!&Np5pXv+fy@{@QN9d|q0hN76tU8EcKoEps7zry5%6b0*`q+8je;CdQ V;USt7kK=Cg{43@-d5@RJ@DDP2Nbvvw literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0002_cart_cartitem.cpython-311.pyc b/core/migrations/__pycache__/0002_cart_cartitem.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56c1714b5d276d8f5ac6411d6fb17f1dbdeee087 GIT binary patch literal 2190 zcmbUiJ!}(K`0ji@-`P&E!NE1LagdO}k%&_eQd(80i4#LgLZO6ISXECKzZY}ne$3rD zkf>A)9Xhf#W2dwg9vC`g=)f3>4$smJ(VHrDVlyHWUHZNAPZGjF`_A{>_xHZP_kGX5 z3=Ks&Shasf^p|0d`LGkM}L1 zRBPl&E+-G?^5J=)H2SEOIa1?O|J&YZD|%2b%2Er>L(H9cFcI#}~CqOpdfo0_w(-pA`yaj^rN z^bN4GhMg7L#j2&5n8sY}y3qPe>(zOvWg$evD~4v>hq=%msRp)c-aRT>O~atko^4h0 zV1~NxEc>CVAw;9i24a4@GC2qZWK?W|>w@T<0Nc(80IGmD7&7c(DtI=_E{H^0)vK~u9l-CKXfMhQGd5MI@qh6nXvxrcSoayJxKoC(G$gq%_4A?{!!h=mz; zvWyjJI5uily^VOF-J=RZupv;nQAkl)4XUE5G^(m_6*LVDcwAK>5`!62RfmlxO}P)X zMnhWxIl>KWA#7FG9d_T)W7Bt*uBmr!&wZsXemOh0K;r`q9lJDE3eIPd-7^lm?r3~Q zuT8_rm0A8&Dc#eYE;=*|Ni69m?vl|mu{bDs%uuwrUF`_a!^b-LmDYf-!4th z&e7CO+l4M!!}FGhYq)1GYBzB2qWg_|1FpHZbH#hMiHpsZrsXw@_z|u`AWre2?c8@8 zS``;rwTk-}WD#2MqRMI=0CoTqjdh1zy8&R6>^_I%dNO!A5~rNmmCyYc_2qNF%KrJ! z+w!z8Pj|whl=Nhw6X%pXlbzm8rvFNgko5S|H9tMRE$*Bp6_r>HNxSW|>!)3k@OG1@ z$=Kx2l0P>2+}jbztvh6FxjnY*k1dnL*SpCa$zR%B^YfQpq)5Kp&X@gsnIx_r5Nt1z z{7gGPN>Aw=<9Z%wv-Hem8lFWG`%%{p^Kp=?}%9y5>*a@~7^SZw-<) z+ga1knj~R$lrW1`X0Za;W1y2x9haVb!5-(6{t*^rn(21e1X`Ks{vS^CTg!Si0CYk;&qJs1X9zde{q6RV f1Dx}Od(W>Ujq*~5I|`mg{lwYVOtAMJX%N-F+@?W5 literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0003_order_orderitem.cpython-311.pyc b/core/migrations/__pycache__/0003_order_orderitem.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf822e4e2aad8bb4635c80b58ed9d849cb5ef995 GIT binary patch literal 2117 zcmbUiO>YxNba(At?>Y|H!C-8hx|LADLByyuQB+lF9Vdpge6U)!Gj`W_ciq`_ zXgCxOJ@m+}QGY<%iX1p_$f2q@_BfX25Y4GlPuz^)L@%A0wH;`3XqolCeP8pw=DqRH z6DXYHz8M^cN?Fi_v1exW+y|N zx*&n*_YzQ{+KLVKOIs01dc@vv)nY(ik&4RQF*J*lyD=Dm3ocO=JbO=lWE0 zJOJB%eXvh_5(DDJZH31KMLabH$ch9bt>{pddDn*cmdw1fH(X7n78APqkOLGDVH~3+ zD#5uR${rPH+p#TX@zKa?Ljk(b92@P8ZX0jfXFBnHI%E5E5<+K~4XfAo_&%LOLT7yF z3m$469^x%yge6BD^TieSXP>txntHCwy|xq%&QJmYghRZrxj^5pz`ah~_I zU=<_iI9Es@*cQ=ngDA^cu@3S;w;tR}2=xJVrI@C=Dbh1d8aGWg(%m*>c)~Q9(>6C? znuw1WP5XDPPRCkfasWHf1<aKO&pj(39 z!a9oumF{}zj^DBBu*8d0+PmvYtgFtOqQ)WQrJwF>Aby7mz{k`7i5<^_(bLx?W4VdPyM2k)1UVAGSSO@HImjITv5RoV z?`3?F@o_TPO-Wbh+|v}Etn?-;WU_*ji!5iHJG1#d$(?!J zcw&>;Dw(}XX1~G>2cz$B?tU+KpXBcMB`H-_L@H-_Dra}J-wZN$k<5KT=5FHKZJc*{ zd57d3oOJt!%EQ%pxD34Hpr1)kXb(Q)51JVsR#B(fu#=|2+I09gHa%=7G|klL*g#(Q z+(y{r#%PBa2hqpj`G3gm3gfd3d!>IIPSHI2h>?6^{eXdfM3!aN6Y_gldN=&-4w2V5 dr?GUvudkhuwZ60;JdBg%$>&_~@&Kub>L1spFq;4X literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 71a8362..ae45f37 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,82 @@ from django.db import models +from django.utils.text import slugify +from django.conf import settings -# Create your models here. +class Category(models.Model): + name = models.CharField(max_length=100, unique=True) + slug = models.SlugField(max_length=100, unique=True, blank=True) + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + def __str__(self): + return self.name + + class Meta: + verbose_name_plural = "Categories" + + +class Product(models.Model): + category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE) + name = models.CharField(max_length=255) + description = models.TextField(blank=True) + price = models.DecimalField(max_digits=10, decimal_places=2) + is_available = models.BooleanField(default=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.name + +class Cart(models.Model): + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True) + session_key = models.CharField(max_length=40, blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + if self.user: + return f"Cart for {self.user.username}" + return f"Cart (session: {self.session_key})" + + @property + def total_price(self): + return sum(item.total_price for item in self.items.all()) + + @property + def total_price_in_kobo(self): + return int(self.total_price * 100) + +class CartItem(models.Model): + cart = models.ForeignKey(Cart, related_name='items', on_delete=models.CASCADE) + product = models.ForeignKey(Product, on_delete=models.CASCADE) + quantity = models.PositiveIntegerField(default=1) + + @property + def total_price(self): + return self.product.price * self.quantity + + def __str__(self): + return f"{self.quantity} of {self.product.name}" + +class Order(models.Model): + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + is_paid = models.BooleanField(default=False) + + def __str__(self): + return f"Order {self.id} by {self.user.username}" + + def get_total_price(self): + return sum(item.product.price * item.quantity for item in self.items.all()) + +class OrderItem(models.Model): + order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE) + product = models.ForeignKey(Product, on_delete=models.CASCADE) + quantity = models.PositiveIntegerField(default=1) + + def __str__(self): + return f"{self.quantity} of {self.product.name}" \ No newline at end of file diff --git a/core/paystack_urls.py b/core/paystack_urls.py new file mode 100644 index 0000000..c6d69f8 --- /dev/null +++ b/core/paystack_urls.py @@ -0,0 +1,10 @@ + +from django.urls import path +from django.shortcuts import render +from . import paystack_views + +urlpatterns = [ + path('payment/success//', paystack_views.payment_success, name='payment_success'), + path('payment/failure/', paystack_views.payment_failure, name='payment_failure'), + path('payment/success_page/', lambda request: render(request, 'core/payment_success.html'), name='payment_success_page'), +] diff --git a/core/paystack_views.py b/core/paystack_views.py new file mode 100644 index 0000000..1388a2f --- /dev/null +++ b/core/paystack_views.py @@ -0,0 +1,37 @@ + +from django.shortcuts import render, redirect +from django.conf import settings +from django.contrib.auth.decorators import login_required +from .models import Cart, Order, OrderItem +from django_paystack.signals import payment_verified + +@login_required +def payment_success(request, reference): + cart = Cart.objects.get(user=request.user) + order = Order.objects.create( + user=request.user, + total_price=cart.total_price, + paid=True, + payment_reference=reference + ) + for item in cart.items.all(): + OrderItem.objects.create( + order=order, + product=item.product, + price=item.product.price, + quantity=item.quantity + ) + cart.items.all().delete() + return redirect('payment_success_page') + +def payment_failure(request): + return render(request, 'core/payment_failure.html') + +@payment_verified.connect +def on_payment_verified(sender, ref, amount, **kwargs): + """ + This signal is called when a payment is successfully verified. + """ + # You can perform actions like updating order status, sending notifications, etc. + print(f"Payment verified for reference: {ref} and amount: {amount}") + diff --git a/core/templates/base.html b/core/templates/base.html index 788576e..21bac82 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,11 +1,64 @@ +{% load static %} - + - {% block title %}Knowledge Base{% endblock %} - {% block head %}{% endblock %} - - - {% block content %}{% endblock %} - - + + {% block title %}VTU Nigeria{% endblock %} + + + + + + + + + + + +
+ {% block content %} + {% endblock %} +
+ +
+
+

© 2025 VTU Nigeria. All Rights Reserved.

+
+
+ + + + \ No newline at end of file diff --git a/core/templates/core/cart_detail.html b/core/templates/core/cart_detail.html new file mode 100644 index 0000000..7841771 --- /dev/null +++ b/core/templates/core/cart_detail.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} +
+

Your Shopping Cart

+ {% if not cart.items.all %} +

Your cart is empty.

+ {% else %} + + + + + + + + + + + {% for item in cart.items.all %} + + + + + + + {% endfor %} + +
ProductQuantityPriceTotal
{{ item.product.name }}{{ item.quantity }}${{ item.product.price }}${{ item.total_price }}
+
+

Total: ${{ cart.total_price }}

+
+ {% csrf_token %} + +
+
+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/checkout_complete.html b/core/templates/core/checkout_complete.html new file mode 100644 index 0000000..5c40cdf --- /dev/null +++ b/core/templates/core/checkout_complete.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +
+

Checkout Complete

+

Thank you for your order. Your order number is {{ order.id }}.

+

We will process your order shortly.

+ Continue Shopping +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 0a3f404..8b3be6b 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,154 +1,43 @@ -{% extends "base.html" %} -{% block title %}{{ project_name }}{% endblock %} +{% extends 'base.html' %} -{% block head %} -{% if project_description %} - - - -{% endif %} -{% if project_image_url %} - - -{% endif %} - - - - -{% endblock %} +{% block title %}Welcome to VTU Nigeria{% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+

Fast & Reliable VTU Services

+

Your one-stop platform for airtime, data, cable subscriptions, and more.

+ Get Started Now
-

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) -
-{% endblock %} \ No newline at end of file + + +
+
+

Our Products

+
+ {% for product in products %} +
+
+
+
{{ product.name }}
+
{{ product.category }}
+

{{ product.description|truncatewords:20 }}

+

₦{{ product.price }}

+
+ {% csrf_token %} + + +
+
+
+
+ {% empty %} +
+

No products available at the moment. Please check back later.

+
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/core/templates/core/payment_failure.html b/core/templates/core/payment_failure.html new file mode 100644 index 0000000..c8e8bb0 --- /dev/null +++ b/core/templates/core/payment_failure.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} +
+

Payment Failed

+

There was an error processing your payment. Please try again.

+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/payment_success.html b/core/templates/core/payment_success.html new file mode 100644 index 0000000..e50029d --- /dev/null +++ b/core/templates/core/payment_success.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block content %} +
+

Payment Successful

+

Your order has been placed successfully.

+

Order ID: {{ order.id }}

+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/registration/signup.html b/core/templates/registration/signup.html new file mode 100644 index 0000000..8f0e0d0 --- /dev/null +++ b/core/templates/registration/signup.html @@ -0,0 +1,22 @@ + +{% extends 'base.html' %} + +{% block title %}Create an Account{% endblock %} + +{% block content %} +
+
+

Create Your Account

+
+ {% csrf_token %} + {{ form.as_p }} +
+ +
+
+

+ Already have an account? Login +

+
+
+{% endblock %} diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/templatetags/__pycache__/__init__.cpython-311.pyc b/core/templatetags/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..860da19abc25a9f2a7b43a5fb5907596ea9b7188 GIT binary patch literal 170 zcmZ3^%ge<81iimSGePuY5CH>>P{wCAAY(d13PUi1CZpdOhFBUF_guqTw$f#Keuo9PL?)EhX2UieX@2&`CG`1C8;Y z!GyuwMB?OzVZ+6D#2WQ-?|a{S-}`a7$JNzkz;pMx8FbOV=^#7CH<=wF8G;8M2_Yg4 zLcjn{TA2YT+9cVttxQ|a(4?#eV4&SfmllMZva#PoCYEK#U!Ce3H`^yM3|e0QS8Z(SH&8J1TCo{OerDc6-uMOcVd-p j_QzJ~#VU`ja^IYg;$UZL5VAc5gbxbF#_z-;yV&*xvrTLt literal 0 HcmV?d00001 diff --git a/core/templatetags/cart_tags.py b/core/templatetags/cart_tags.py new file mode 100644 index 0000000..304099b --- /dev/null +++ b/core/templatetags/cart_tags.py @@ -0,0 +1,7 @@ +from django import template + +register = template.Library() + +@register.filter +def mul(value, arg): + return value * arg diff --git a/core/urls.py b/core/urls.py index 6299e3d..751c174 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,13 @@ -from django.urls import path - -from .views import home +from django.urls import path, include +from .views import index, signup, add_to_cart, cart_detail, checkout, initiate_payment, verify_payment urlpatterns = [ - path("", home, name="home"), -] + path("", index, name="index"), + path("signup/", signup, name="signup"), + path("accounts/", include("django.contrib.auth.urls")), # for login, logout, etc. + path('cart/', cart_detail, name='cart_detail'), + path('cart/add//', add_to_cart, name='add_to_cart'), + path('checkout/', checkout, name='checkout'), + path('initiate-payment/', initiate_payment, name='initiate_payment'), + path('verify-payment/', verify_payment, name='verify_payment'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index c9aed12..fa09d91 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,134 @@ -import os -import platform +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth import login +from django.views.decorators.http import require_POST +from django.contrib.auth.decorators import login_required +from django.conf import settings +import requests +import json -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() +from .models import Product, Category, Cart, CartItem, Order, OrderItem +def index(request): + """Render the new landing page.""" + products = Product.objects.filter(is_available=True) + categories = Category.objects.all() 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", ""), + 'products': products, + 'categories': categories, } return render(request, "core/index.html", context) + +def signup(request): + """Handle user signup.""" + if request.method == 'POST': + form = UserCreationForm(request.POST) + if form.is_valid(): + user = form.save() + # You can log the user in directly if you want + # login(request, user) + return redirect('login') + else: + form = UserCreationForm() + return render(request, 'registration/signup.html', {'form': form}) + +def _get_cart(request): + if request.user.is_authenticated: + cart, created = Cart.objects.get_or_create(user=request.user) + else: + session_key = request.session.session_key + if not session_key: + request.session.create() + session_key = request.session.session_key + cart, created = Cart.objects.get_or_create(session_key=session_key) + return cart + +@require_POST +def add_to_cart(request, product_id): + cart = _get_cart(request) + product = get_object_or_404(Product, id=product_id) + quantity = int(request.POST.get('quantity', 1)) + + cart_item, created = CartItem.objects.get_or_create(cart=cart, product=product) + if not created: + cart_item.quantity += quantity + else: + cart_item.quantity = quantity + cart_item.save() + + return redirect('cart_detail') + +def cart_detail(request): + cart = _get_cart(request) + return render(request, 'core/cart_detail.html', {'cart': cart}) + +@login_required +def checkout(request): + cart = _get_cart(request) + if not cart.items.all(): + return redirect('index') + return render(request, 'core/cart_detail.html', {'cart': cart}) + +@login_required +def initiate_payment(request): + cart = _get_cart(request) + if not cart.items.all(): + return redirect('index') + + url = 'https://api.paystack.co/transaction/initialize' + headers = { + 'Authorization': f'Bearer {settings.PAYSTACK_SECRET_KEY}', + 'Content-Type': 'application/json', + } + data = { + "email": request.user.email, + "amount": cart.total_price_in_kobo, + "callback_.pyurl": request.build_absolute_uri(f"/verify-payment/"), + } + + try: + response = requests.post(url, headers=headers, data=json.dumps(data)) + response_data = response.json() + if response_data['status']: + # store the reference in the session, so we can retrieve it in the callback + request.session['payment_ref'] = response_data['data']['reference'] + return redirect(response_data['data']['authorization_url']) + else: + return render(request, 'core/payment_failure.html', {'error': response_data['message']}) + except requests.exceptions.RequestException as e: + return render(request, 'core/payment_failure.html', {'error': f"An error occurred: {e}"}) + +@login_required +def verify_payment(request): + ref = request.GET.get('reference') + if not ref: + # if the reference is not in the get request, check the session + ref = request.session.get('payment_ref') + if not ref: + return redirect('payment_failure') + + url = f'https://api.paystack.co/transaction/verify/{ref}' + headers = { + 'Authorization': f'Bearer {settings.PAYSTACK_SECRET_KEY}', + } + + try: + response = requests.get(url, headers=headers) + response_data = response.json() + if response_data['status']: + if response_data['data']['status'] == 'success': + cart = _get_cart(request) + order = Order.objects.create(user=request.user, is_paid=True) + for item in cart.items.all(): + OrderItem.objects.create(order=order, product=item.product, quantity=item.quantity) + cart.items.all().delete() + # clear the payment reference from the session + if 'payment_ref' in request.session: + del request.session['payment_ref'] + return render(request, 'core/payment_success.html', {'order': order}) + else: + return render(request, 'core/payment_failure.html') + else: + return render(request, 'core/payment_failure.html') + except requests.exceptions.RequestException as e: + return render(request, 'core/payment_failure.html', {'error': f"An error occurred: {e}"}) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e22994c..c9b1117 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,8 @@ -Django==5.2.7 +Django +django-environ +gunicorn +psycopg2-binary +whitenoise +django-paystack mysqlclient==2.2.7 python-dotenv==1.1.1 diff --git a/static/css/custom.css b/static/css/custom.css new file mode 100644 index 0000000..073110b --- /dev/null +++ b/static/css/custom.css @@ -0,0 +1,107 @@ + +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Poppins:wght@700&display=swap'); + +:root { + --primary-color: #0D0D2B; + --secondary-color: #3671E9; + --accent-color: #2B076E; + --bg-light: #FFFFFF; + --bg-gray: #F8F9FA; + --text-light: #FFFFFF; + --text-dark: #0D0D2B; + --font-headings: 'Poppins', sans-serif; + --font-body: 'Inter', sans-serif; +} + +body { + font-family: var(--font-body); + background-color: var(--bg-light); + color: var(--text-dark); +} + +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-headings); + font-weight: 700; +} + +.btn-primary { + background-color: var(--secondary-color); + border-color: var(--secondary-color); + padding: 12px 30px; + font-weight: 500; + transition: all 0.3s ease; +} + +.btn-primary:hover { + background-color: #2d62c8; + border-color: #2d62c8; +} + +.navbar-brand { + font-family: var(--font-headings); + font-size: 1.5rem; + color: var(--text-light) !important; +} + +.hero { + background: linear-gradient(90deg, var(--accent-color) 0%, var(--primary-color) 100%); + color: var(--text-light); + padding: 100px 0; + text-align: center; +} + +.hero h1 { + font-size: 3.5rem; + margin-bottom: 20px; +} + +.hero p { + font-size: 1.2rem; + margin-bottom: 40px; +} + +.services-section { + padding: 80px 0; + background-color: var(--bg-gray); +} + +.service-card { + background-color: var(--bg-light); + border: none; + border-radius: 15px; + padding: 30px; + text-align: center; + box-shadow: 0 10px 30px rgba(0,0,0,0.07); + transition: transform 0.3s ease; +} + +.service-card:hover { + transform: translateY(-10px); +} + +.service-card .icon { + font-size: 3rem; + color: var(--secondary-color); + margin-bottom: 20px; +} + +.form-container { + max-width: 500px; + margin: 80px auto; + padding: 40px; + background-color: var(--bg-light); + border-radius: 15px; + box-shadow: 0 10px 30px rgba(0,0,0,0.07); +} + +.form-container h2 { + text-align: center; + margin-bottom: 30px; +} + +.footer { + background-color: var(--primary-color); + color: var(--text-light); + padding: 40px 0; + text-align: center; +}