From b2cb32384eee5f015170949020323cb4105e000c Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 25 Jan 2026 02:29:45 +0000 Subject: [PATCH] adding email otp --- core/__pycache__/forms.cpython-311.pyc | Bin 18971 -> 20487 bytes core/__pycache__/mail.cpython-311.pyc | Bin 0 -> 2475 bytes core/__pycache__/models.cpython-311.pyc | Bin 38752 -> 38874 bytes core/__pycache__/views.cpython-311.pyc | Bin 46036 -> 48464 bytes core/forms.py | 18 +++- core/mail.py | 40 ++++++++ ...tpcode_email_alter_otpcode_phone_number.py | 23 +++++ ...alter_otpcode_phone_number.cpython-311.pyc | Bin 0 -> 1032 bytes core/models.py | 7 +- core/templates/registration/login.html | 34 ++++++- core/templates/registration/register.html | 22 +++++ core/templates/registration/verify_otp.html | 14 ++- core/views.py | 91 ++++++++++++------ 13 files changed, 211 insertions(+), 38 deletions(-) create mode 100644 core/__pycache__/mail.cpython-311.pyc create mode 100644 core/mail.py create mode 100644 core/migrations/0027_otpcode_email_alter_otpcode_phone_number.py create mode 100644 core/migrations/__pycache__/0027_otpcode_email_alter_otpcode_phone_number.cpython-311.pyc diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index e0ce3959d340c559fbfd14fb826f714e51e2080d..cedff70a2f0810a893544334554275015b99a652 100644 GIT binary patch delta 4736 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 diff --git a/core/__pycache__/mail.cpython-311.pyc b/core/__pycache__/mail.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf89663fca48a54282ada82fddceea17efe22e11 GIT binary patch literal 2475 zcma)8O>7fK6rS~ZcW3Qnosbw3P}^-u(i%`q1hf#LBFAw8C~>O91>pmh@ovnfwl|tx zC&98)A9_H&lp9DD5+|Cf7Y;oXsg($7C64RXQmvIDRqCm?NT?Fx)Hl2Si9>05o_+IX z=FNL==gs%l-}LtS5K#2mb}k3R0LHO48nwWAdJ)hoLf?WAomm z*O9$BF3%?g26>Q*R&dLZDn4Wo`VJnKl0Jp|0ww(l54=ZVmQYCei!Q}Oj4c{!K}qG) zS`G%-P)$=SWDSsevLLG)W-6C1-O6~HwrD57LxX)D2N!pd*>;2=(lYy2XD~6md$P0;2GRhp-9N1-%!O)nC8O(oj0mPwnMUU2o6 zbt2D4mLiLCG_p9mP&)Nyda`Cr%A+4pS0Tgp(!O+nI(K}^a7#?t)a69!Xlx^+t~&nK zZ)!P}E-7$loJbwzNjW|f5^W|$Xm0ES)3KSzr*nza%;Lg)DmEXvG)L(5vH6^y&8o!q zYK3Lp7Tb1hQEj=fS;HhD!R8#pgl51NjN*+IHDlUBUNww#R<(W0M9-%j6+x{IDE(909yn|fXi7jF~`W-+X8sF|XvlkmDuJ~LL+88w{Ii5hl}dG(ep zIpEOERo6H<2$tv_Gq!|w!+&FmT&edvs*UNLk9o&EC{d*rhxco?};R|(q!NM1Iu=JJxOTRUEaw`Z7 zS`AOs@q~pZ8i9e^=8GNw;4boT!488hzvH1_@sKq<`;+lx;_=##Yal|a;fr;A(ZUxS zfx+9w*6#(-E(a!{Hx9vDd>@XCqU}-N)CjvBJ~s6Z`}7?Sp?fl3GP$^Qks;whsR zqygRlHh>%hoGW_(41C4=8lZDs^KI`H9-uGMAY5{R4=~#geW$LvIuD?ryh;l1#39^g zyCDkT2Ot9YrOqA){DQgH=AP2h(Njcopff59-IiYieqTk{6cj&z-;wr!zo!)53x2?J z!AxgNxp~hi{>+=IoL2H$p;-mqjjQXg=utQTmJ8{;gJs&6_&D%MxrBW&db>gL`+V~* zfF^yVV051^$3`j82EOCeNR&ctn9AMo-xsV?1bl=Zn^Oh|O`_cwU5FM$0Ve17`Ou7jwB(^Hj(II#1I;27wvWql0x|BnSh0 z&_QuCzV@~wury3J{06LzL`4yP$o3>+qxNH9TF%(-xj?Lwjl~W(`ict%jp@9JO%t4bVOR%hI#{?JGZD zv4-cN)o{Fy;}(wpH@Y96j2uVX$9+@B*zGgNrbgMPqa4VgUR$8W7s|HXr>vw4S$&)q zzHxdr+~VwSPZyTmIzH5#DVkc&7&l3}VC0zH@mcS z5_;y}qUXSolbVQ0(10_gs};1mt`2HfuySQYoIZ!yETw(b+-ok(CTh~ mh=*?uJ2>$>k{9&&E literal 0 HcmV?d00001 diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 673917ca9ff66dd0131f2ead83c562778464ec38..8e53bb39c12ddc5bc91c39c37f2572c22df8ff8d 100644 GIT binary patch delta 3107 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 diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index d5295116f11ff715b9eddbe63e56ed0739d242a8..1c836550fd8aa9ba8522ea50c9994d3526359bb9 100644 GIT binary patch delta 11558 zcmcgydw5gFmDknVPx&FsmTma~HXy%^0rSS@VK6ZeYy&|_5JLCbmeGs3*E}4#B7rP4 zNfU>mX&OiZ+lC~?+0dxpSK2gR$!@kyUqRwi7J9dAoNd!IO}3D9lk~OCo-!@IyKl84yqY}}c-Q#WI@bEuIo7df zi?_qK-m#vQ3qhF;jt#6_cfsSF8A5jhckT@CF5u3a!F2-HK7+fP zuL09sd@ayDAW=6%!VTPdTBXSi^H6G-Q7`aI`NpH1qYrAEX4Lirw|NG4Kv>PspTYHj zY|9MpAaEBTH$27fdx5=hhOig7i)L_rz-^tu_4A9t=m4m)WJX<(xAv&)%ihD_uyb-& z?||z-#HO9c<)zD}HAA8l@O1_HJ^qdW@zD<5Ps-JDwkY^{XFv)%1)s~~mCfwg9q>yo zw-hnzt(uG#&>LBaWEHJT>(Xt2;v71X_G(53lr-@F`*9tuKiy6rNdK!wO-ks48C$u_ z^m@i?)R#GXZZ>Z^rv@*;zw+U8P8&ybAzer{m^;DCrQgn+XAbFjgEh%hjTl0@Gdc9E zp`d(5CnLs?kvHsRosuumw=xT>4P$vz*TzViWS{6v6*7d<1`B9&nMF&QrFy!xwUBG3 z%|~kKQ(2jG;7G0-n6G3ZGrX^u-pI=3{ssCiGLKniblXfH?Ji!-7h#Xd5j@DyVATvDZf?Ww05vP-utNlAoMd}&AcaY>$l-^sfq*Yg}}{HG@Rim67&0*u}; z6|4W$NdKpz!crjxDh>sPNCm5D0BP)&9ySHl!|fokfUe3JGMAG=9HI?}(Diu;jL=VC z&k5;gA-kJy%w1zA^;qVS%tJY?$KMx_IVUmTlTIKcKu8^w={Ixp!x>~9vNOR(rzl90 z$KNlKe%7u}5Jb`kMY0Q@)BA?JUT1J1;1`%BilG|tmaOrK{UR=aiqwa!$3{lQ4>0Jx z@Yn+Xq7{68Sam($7SFG~O25&UMc3xVxY^W_zpMIM*{pb3oktIWQs0us%%-89o9`E}uI=IS&3adX4*b=RylSFH_KtPRhFE;?e?4RPy+;~U3K z*2i~TF;zuPRo5-HxTW?&b=0yrW?3A!EFRTd&#ydF{apT7Q7nIcJb(U8l{R+|cimii za?_c)FEmH%ow3^8@!H)nvny_PMa{13C9|VdD`F)p<0UJj=9TZ5OT#BOpJ{!eJ=)-m z)$NYgr9?sc9X8;_;;3b9%(6CaSqlSN3Zq4vVwTNu%jRhJ9Z|>KamSwMR(H(eMs4Oj zSA3x-mcJyPzXWxWChM{%R_l(}x?^TOZswzAUeRUlWMz`N?2FZrcpZtE#kg6FvOl`W zyo>9k(E+P^sgAxn+dhy;nT-5l+{Elzg%`=g-K)ZFM!JCwDhseDHUmkkYSV~Eg=Kldbr9A z#WFrCWN>CeV6YEVoV{-@z1PwZ&Q4+9RQ2G=l&TxcnYxe>3kP$O{f(r7bZ$yIl;)g( zS*X!ZsmV*Wf*KudD9qr}=+eU3<#~MmR5w5`<_Xy})Yw#;ddx3N+pgQg%%WCJ8bWgxrZG2R&rHKdfPhA{&B3Bp46{ zHk!SZ455UC1W}3vk<3Pdx{`fJ2om%sgFn&^C0Xlr0R-O9ilS@3KyVu53^x&6l7Q+G z7>NY>JYGT3B+PVAfijs8 zYRX+2UORQsjlk|cXaoHi9r(55GI*s#+SZ$46^^{1S3 z_mN@-I)5oIEoe-RhzQ3Q@Rktg;Z9i1dALOF9KqmCfCn9|Fc-?%K_akhT1lqZ;u!9x zn=49p`js@N!o~9hR#-#D?Owqp3Kg(WDt2JX)gBC11V}|9_Xati$KOD3{)iLFZXj|F zEO<}fAvT5=b9u6dc2;y~-B3xtUa>rlEldHQ{#-E}#ak)-mj1NewYBj=`)Ci>G#qZ9Kk1x1VsiZB2< ziQG{nm^>0VCaDO+lfzIFG3Jwyp=4E=ZgzfgDn_4%`g17Qsiy6-^2241A9!xZ*j-W6 z(wJ#!+_dz#_F9JNkrfZD_~NRER(+_dOxwU+%P;)OiW4gyU3GHR@wL}1#V5VT*I%>R z;el3CkP$(Cy!!l5)1qvC>nf0-fx_C)l)LfTH*Q&l%6Sb^}Sysd?E0lz->gzSn zxyJ@#`K|H%*8d}A3s1_~7R5?h<0Y+8b1UR*$uwzt$_8oPcwt$*u=b{Evg*->V61Lm zyl!92OyXt|HIu1|j@zo@wnd}qio&UMFPzNxdSaH|xTQCG$FAsZcYOCibcZKq@!TTc zBY!jByQLk0SY0q)7mS(r#m)Po?C+gS^FwrCc4>N9%v2FKRUFq|r}u6u)OSL7xH)=w znVE~zOS6~KH^Q^5YF;xHBu3OBPBck6y0xa5TB~fWx@q+qfF~Vv#Xf+?szH77iPtN~ zPr&gV)K@iE17}+L*1{?yFzNw2(d>~JFe$uL4>{cfGqJh>k}2afNs zWAVAS$j0(ElgB`qd<6-nS`69X%=ZMA5l_hxB)vd7$Wbi*1O4lqvhXo19!K&;Bo6|y zTc;;ah!-poQXnOWkAFLtQgW9sq3|grm{gI6kucO{&=)>|&(qSOH01Zl!FiV6b(F^k~=6m-?mmPq=SjNG4 z#=)reAnA-8w7;v>*mHx1?&w`*W>)O|D>f8)-{~SEQHoBdzY1+V@rw=vt z+uKke3-_h~aVkkIVZE?>2aIHv29wpaw0X{wH7Koxy9Plv2nXHXAzqNvA!`>720cXJ zmE2h84h;DvmSx+sl`UwL-qT#Ae+hd#PoHV7&yHa6Ti6>HBpr0TxzMy8SuFaHd=$~o ze_qXXMlLL<)s$|XsKdzZc%GB>?tstdk$TAz`f2Mu+_O|%{Gzh`S{41-UdN0uU}F}d zka;PwFY7uwyVmt6SwD-wXR!+XLu^RekYN5#JV4|OmK`K+CRlLxAln%9;vv8vV3!@F zltnNL;8ZHFOKigQl_m3v*JG<}i43{-Iw!6zFdVBQpDnqEgJZPcZ;hN>_6PNBwoaK= zXfjxA^!P=|<#!7m1?s$w9&|=If`{G4!(^grd7%l%uF;`HozKiNfIHEy9#WD4JO6cKjPzrTbZ)5 z9+J&|IL2bo$a;2*C@Pt3!VDJJMsdIs^a*|`X-jd!7&z=p&SWKd8#)<|gt|&`RiObd zZPIb!2?Kh`3#h@@P^tiq&_binMfM63tZ28;Fd+H7FErm_YBrO`6bkh()KN0z_)+e@?C8KTkI?rClrLM){)+a?aWW_fIRs% zl6R5(2FZI!ev9ONBp=WP+iJ_lvG_a^wz_U$=_ZojAvs0Au+3xsArxhOfOz0E16VG= z?ZyrI=WX|}X=Lr}Wz(@@6q`>2(<H;D0s+rH zN}!~%NpParAEENA81WlnvJR_0j(lmy2H13H?IYI6jh#oiLIf3<)N%My{HiQa`oNu) zdd56QzjJ;cRy^d*eC@qiY+GABnMW5lL!3rc*$oV^<%p2u8R;`*>}I8=ANS0o%3OX$k|=O zouYNSdonNr$nTN-H+^9D$}kHUR9ta7b2XbI1M*ntAVex&z`&BT;q8Q{-!CMQgH3D7 zS_Yh-V3SXgu=zXzr38A)dhk8k_y_DOs|9Tceh!8zW~RH~nk*=>In%>@y%`yHb>YIs zePDb~4X4M6B48w6dw0s5Y5KE}I)H*`T@o9$&>zzzi6@C6i7+U*l_AqWmZg4rrP>0))bRpS7FAvTMvp{9q^&xWrh&^W#{CyX>%%z1`YD2;r8~Oo7QdaB_GkhlJZ$>-5_ki9WQ~t;XN+CkCP5TM6;#y4qYW4)EB}a zi^CZKWUKDRa(hNk-Tf45K zeOp(rv!i>#?Xi7>z4x*1)$wHwF8ER!J7&j3LFCMAZ z+nGvKKC;vZzlec^pVZOPqszJ9(Va)<%)yYf7s{IT>w1-U7;KW;aFli=tTzvR=4e%z zjSs(jnT*_QR@t&a-Ic&%W2ayqJR43A}%py zcU6+g3^EIQLh{u}&;9?L^ikunqpi#etup+-quLY0!>JSp6`_?Xaa^B`-5`0H{`S~H zSeq5cKUQD$iKo+Z4{3!84-0w4&CI0OqbX5ln|V#8_`vskASO+`db zj0m=m5)5WW{u5Fxx_9(W(4^_Cm5z`8Mn8r_9HVbM8muoyF`Tx|uwrcZz*Y%boSd5v%+Y&oFxS$mlw{}`vw1y@Hv@#YTf(=x|TKKkWb0M z61y|@MBG49n*_?`C>H>I9zUGGJua_E1dw?jOcEh<=z}x_n&8>J^5m~3pW-&soX1)! zQI;7uZ0;8NhWva*AG~?!R`6zN1u3QcW9`$=dbqGAO#<`Dv>l-hMLtHNt!AUY$B^iYFpU}pyd}cwBhJ8{L6+#VxiL?K zm={e(lBJ>br+%dbSfuv!Fv7*TrydKJqd+f=m5BPpJD+6K_n{Un>I55fy*Y$>N4H1N`$1wsmZovf{04%Yi7Py(-FC=E zr_=B92~HClC;3xL+uc%`gkWfUPVgAaQn{2PDG*)&u#IaXI+dG~^Xl zlBb~#{#0UJ5&(=Sr$cAxA>#~_F}wqC1<#)njmfJ mW)6QmFp2hYbGZ)grfL%H<|?^Ez?wumxX6bW-coDPCI1UCm^M2A delta 9743 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 C&?c4u diff --git a/core/forms.py b/core/forms.py index df3ac14..ab9441c 100644 --- a/core/forms.py +++ b/core/forms.py @@ -1,7 +1,7 @@ from django import forms from .models import Truck, Shipment, Bid, Profile, Country, OTPCode, City, TruckType, AppSetting, ContactMessage from django.utils.translation import gettext_lazy as _ -from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.contrib.auth.models import User class UserRegistrationForm(UserCreationForm): @@ -11,6 +11,7 @@ class UserRegistrationForm(UserCreationForm): country_code = forms.ChoiceField(choices=[], widget=forms.Select(attrs={'class': 'form-select'})) phone_number = forms.CharField(max_length=20, required=True, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '123456789'})) subscription_plan = forms.ChoiceField(choices=[('MONTHLY', _('Monthly')), ('ANNUAL', _('Annual'))], required=False, widget=forms.Select(attrs={'class': 'form-select'}), label=_('Subscription Plan')) + otp_method = forms.ChoiceField(choices=[("whatsapp", _("WhatsApp")), ("email", _("Email"))], initial="whatsapp", widget=forms.RadioSelect, label=_("Receive verification code via")) accept_terms = forms.BooleanField(required=True, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}), label=_('I have read and agree to the Terms and Conditions and Privacy Policy')) class Meta(UserCreationForm.Meta): @@ -199,3 +200,18 @@ class ContactForm(forms.ModelForm): 'subject': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Subject')}), 'message': forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'placeholder': _('Your Message')}), } + +class CustomLoginForm(AuthenticationForm): + otp_method = forms.ChoiceField( + choices=[("whatsapp", _("WhatsApp")), ("email", _("Email"))], + initial="whatsapp", + widget=forms.RadioSelect, + label=_("Receive verification code via") + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for field in self.fields.values(): + if not isinstance(field.widget, (forms.RadioSelect, forms.CheckboxInput)): + field.widget.attrs.update({"class": "form-control"}) + diff --git a/core/mail.py b/core/mail.py new file mode 100644 index 0000000..18ad8d1 --- /dev/null +++ b/core/mail.py @@ -0,0 +1,40 @@ +import logging +from django.core.mail import send_mail +from django.conf import settings +from django.utils.translation import gettext_lazy as _ + +logger = logging.getLogger(__name__) + +def send_otp_email(email, code): + """ + Sends an OTP code via email. + """ + subject = _("Your verification code for MASAR CARGO") + message = _("Your verification code for MASAR CARGO is: %(code)s") % {"code": code} + from_email = settings.DEFAULT_FROM_EMAIL + recipient_list = [email] + + try: + send_mail(subject, message, from_email, recipient_list) + logger.info(f"OTP email sent to {email}") + return True + except Exception as e: + logger.exception(f"Exception while sending OTP email: {str(e)}") + return False + +def send_contact_message(name, email, message): + """ + Sends a contact message to the admin. + """ + subject = _("New contact message from %(name)s") % {"name": name} + full_message = f"Name: {name}\nEmail: {email}\n\nMessage:\n{message}" + from_email = settings.DEFAULT_FROM_EMAIL + recipient_list = settings.CONTACT_EMAIL_TO + + try: + send_mail(subject, full_message, from_email, recipient_list) + logger.info(f"Contact message from {email} sent to admins") + return True + except Exception as e: + logger.exception(f"Exception while sending contact message: {str(e)}") + return False diff --git a/core/migrations/0027_otpcode_email_alter_otpcode_phone_number.py b/core/migrations/0027_otpcode_email_alter_otpcode_phone_number.py new file mode 100644 index 0000000..6a6c3a1 --- /dev/null +++ b/core/migrations/0027_otpcode_email_alter_otpcode_phone_number.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.7 on 2026-01-25 02:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0026_remove_banner_admin_phone_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='otpcode', + name='email', + field=models.EmailField(blank=True, max_length=254, null=True), + ), + migrations.AlterField( + model_name='otpcode', + name='phone_number', + field=models.CharField(blank=True, max_length=20, null=True), + ), + ] diff --git a/core/migrations/__pycache__/0027_otpcode_email_alter_otpcode_phone_number.cpython-311.pyc b/core/migrations/__pycache__/0027_otpcode_email_alter_otpcode_phone_number.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..356f7782cf940db87d7ede168373d680416bcd9f GIT binary patch literal 1032 zcmah{O;6N77@ldn-EDzHnoTrOK!K=xp+t>bhzT(&*(eu@hbCt1?u@%#+UYvGRXoYT zgBKHS`~jmF`2igH36Shz)2kSPLJL;H8Gp zPiYKRuCE*~g0h7uqBukm_Hd+m8b%s=f~fWpQJvw2J^+0M?Z?pabZR_DrQD$mm$cN5 zf|~H#LCjmJ8O4-^t?B?Nzp5Gd9)AI`g&6WMMV>}+4STwVn9&$C1*NIJe%4u+DK|W8_Wa)8r4{^^w#|Ro%=+v zD1OUG#pj#};?pSLq*;$SBR;1jk_Yo~yIBPq(j1HYAY48C1#Oh{G*=0Iz6Nyp>m+2n z)~=@pPr?vFwxW%2KVsmMGWR-QAx%enxp@**m^ds#q7~?_K-5t}Qky_cnuHAWQ9|A% zekkG6qO8d{WxN`&mXMd17M3V|EYk`@n)V~vo^qu2QrKpqr&|x}eqXiYChK4AcXSLO zbo$Dz#}RXrO2XTT%hp*nX~)8S7mKx4)2}jDlI8ySC0w9wf%GXMiV@<=Vng5GBhEG7 z3lqvHA(hr=5F5jtnaEnqbggJ^Jk4xmUHfEr3Rkd;8AvfwSCkmj6YKjX35;S#B{o z=x?QDq!w-XycW;VisD!?+nZoQ9#dzhKszlg_AcsyxB(`HxAg) - + {{ form.username }} + {% if form.username.errors %} + {% for error in form.username.errors %} +
{{ error }}
+ {% endfor %} + {% endif %}
- + {{ form.password }} + {% if form.password.errors %} + {% for error in form.password.errors %} +
{{ error }}
+ {% endfor %} + {% endif %}
- + +
+ +
+ {% for radio in form.otp_method %} + + + {% endfor %} +
+
+ +

{% trans "Don't have an account?" %} {% trans "Register" %}

diff --git a/core/templates/registration/register.html b/core/templates/registration/register.html index e9596cf..c384426 100644 --- a/core/templates/registration/register.html +++ b/core/templates/registration/register.html @@ -47,6 +47,28 @@
{% elif field.name == 'phone_number' %} {# Handled above #} + {% elif field.name == 'otp_method' %} +
+ +
+ {% for radio in field %} + + + {% endfor %} +
+ {% if field.errors %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} + {% endif %} +
{% elif field.name == 'subscription_plan' %} {% if subscription_enabled %}
diff --git a/core/templates/registration/verify_otp.html b/core/templates/registration/verify_otp.html index 5e7e55b..f7ddf66 100644 --- a/core/templates/registration/verify_otp.html +++ b/core/templates/registration/verify_otp.html @@ -14,10 +14,18 @@

{% trans "Verify Your Account" %}

- {% if purpose == 'registration' %} - {% trans "We have sent a verification code to your WhatsApp number. Please enter it below to complete your registration." %} + {% if otp_method == 'email' %} + {% if purpose == 'registration' %} + {% trans "We have sent a verification code to your email address. Please enter it below to complete your registration." %} + {% else %} + {% trans "We have sent a verification code to your email address. Please enter it below to log in." %} + {% endif %} {% else %} - {% trans "We have sent a verification code to your WhatsApp number. Please enter it below to log in." %} + {% if purpose == 'registration' %} + {% trans "We have sent a verification code to your WhatsApp number. Please enter it below to complete your registration." %} + {% else %} + {% trans "We have sent a verification code to your WhatsApp number. Please enter it below to log in." %} + {% endif %} {% endif %}

diff --git a/core/views.py b/core/views.py index 6360e66..d1bd06d 100644 --- a/core/views.py +++ b/core/views.py @@ -10,6 +10,7 @@ from .models import ( AppSetting, Banner, HomeSection, Transaction, ContactMessage, Testimonial, WhatsAppConfig ) from .forms import ( + CustomLoginForm, TruckForm, ShipmentForm, BidForm, UserRegistrationForm, OTPVerifyForm, ShipperOfferForm, RenewSubscriptionForm, AppSettingForm, ContactForm @@ -19,6 +20,7 @@ from django.utils.translation import gettext as _ from django.db.models import Q from django.contrib.auth.models import User from .whatsapp import send_whatsapp_message +from .mail import send_otp_email, send_contact_message from django.contrib.auth.forms import AuthenticationForm from django.core.mail import send_mail from django.conf import settings @@ -70,23 +72,35 @@ def register(request): registration_data = { 'username': form.cleaned_data['username'], 'email': form.cleaned_data['email'], - 'password': form.data['password1'], # We need raw password to create user later + 'password': form.data['password1'], 'role': form.cleaned_data['role'], 'phone_number': form.cleaned_data['phone_number'], 'country_code': form.cleaned_data['country_code'], 'subscription_plan': form.cleaned_data.get('subscription_plan', 'NONE'), + 'otp_method': form.cleaned_data.get('otp_method', 'whatsapp'), } request.session['registration_data'] = registration_data # Send OTP full_phone = f"{registration_data['country_code']}{registration_data['phone_number']}" - otp = OTPCode.generate_code(full_phone) - msg = _("Your verification code for MASAR CARGO is: %(code)s") % {"code": otp.code} - if send_whatsapp_message(full_phone, msg): - messages.info(request, _("A verification code has been sent to your WhatsApp.")) - return redirect('verify_otp_registration') + email = registration_data['email'] + otp_method = registration_data.get('otp_method', 'whatsapp') + + if otp_method == 'email': + otp = OTPCode.generate_code(email=email) + if send_otp_email(email, otp.code): + messages.info(request, _("A verification code has been sent to your email.")) + return redirect('verify_otp_registration') + else: + messages.error(request, _("Failed to send verification code to your email.")) else: - messages.error(request, _("Failed to send verification code. Please check your phone number.")) + otp = OTPCode.generate_code(phone_number=full_phone) + msg = _("Your verification code for MASAR CARGO is: %(code)s") % {"code": otp.code} + if send_whatsapp_message(full_phone, msg): + messages.info(request, _("A verification code has been sent to your WhatsApp.")) + return redirect('verify_otp_registration') + else: + messages.error(request, _("Failed to send verification code. Please check your phone number.")) else: messages.error(request, _("Please correct the errors below.")) else: @@ -108,7 +122,13 @@ def verify_otp_registration(request): if form.is_valid(): code = form.cleaned_data['otp_code'] full_phone = f"{registration_data['country_code']}{registration_data['phone_number']}" - otp_record = OTPCode.objects.filter(phone_number=full_phone, code=code, is_used=False).last() + email = registration_data['email'] + otp_method = registration_data.get('otp_method', 'whatsapp') + + if otp_method == 'email': + otp_record = OTPCode.objects.filter(email=email, code=code, is_used=False).last() + else: + otp_record = OTPCode.objects.filter(phone_number=full_phone, code=code, is_used=False).last() if otp_record and otp_record.is_valid(): otp_record.is_used = True @@ -141,40 +161,52 @@ def verify_otp_registration(request): else: form = OTPVerifyForm() - return render(request, 'registration/verify_otp.html', {'form': form, 'purpose': 'registration'}) + return render(request, 'registration/verify_otp.html', {'form': form, 'purpose': 'registration', 'otp_method': registration_data.get('otp_method', 'whatsapp')}) def custom_login(request): if request.method == 'POST': - form = AuthenticationForm(request, data=request.POST) + form = CustomLoginForm(request, data=request.POST) if form.is_valid(): user = form.get_user() profile = user.profile - if not profile.phone_number: - messages.error(request, _("Your account does not have a phone number. Please contact admin.")) - return redirect('login') + otp_method = form.cleaned_data.get('otp_method', 'whatsapp') - # Store user ID in session temporarily + # Store user ID and method in session temporarily request.session['pre_otp_user_id'] = user.id + request.session['pre_otp_method'] = otp_method - # Send OTP - full_phone = profile.full_phone_number - otp = OTPCode.generate_code(full_phone) - msg = _("Your login verification code for MASAR CARGO is: %(code)s") % {"code": otp.code} - if send_whatsapp_message(full_phone, msg): - messages.info(request, _("A verification code has been sent to your WhatsApp.")) - return redirect('verify_otp_login') + if otp_method == 'email': + if not user.email: + messages.error(request, _("Your account does not have an email address. Please use WhatsApp or contact admin.")) + return redirect('login') + otp = OTPCode.generate_code(email=user.email) + if send_otp_email(user.email, otp.code): + messages.info(request, _("A verification code has been sent to your email.")) + return redirect('verify_otp_login') + else: + messages.error(request, _("Failed to send verification code to your email.")) else: - # If WhatsApp fails, maybe allow login but warn? Or strictly enforce? - # For now, strictly enforce - messages.error(request, _("Failed to send verification code. Please check your connection.")) + if not profile.phone_number: + messages.error(request, _("Your account does not have a phone number. Please contact admin.")) + return redirect('login') + + full_phone = profile.full_phone_number + otp = OTPCode.generate_code(phone_number=full_phone) + msg = _("Your login verification code for MASAR CARGO is: %(code)s") % {"code": otp.code} + if send_whatsapp_message(full_phone, msg): + messages.info(request, _("A verification code has been sent to your WhatsApp.")) + return redirect('verify_otp_login') + else: + messages.error(request, _("Failed to send verification code. Please check your connection.")) else: messages.error(request, _("Invalid username or password.")) else: - form = AuthenticationForm() + form = CustomLoginForm() return render(request, 'registration/login.html', {'form': form}) def verify_otp_login(request): user_id = request.session.get('pre_otp_user_id') + otp_method = request.session.get('pre_otp_method', 'whatsapp') if not user_id: return redirect('login') @@ -185,8 +217,11 @@ def verify_otp_login(request): form = OTPVerifyForm(request.POST) if form.is_valid(): code = form.cleaned_data['otp_code'] - full_phone = profile.full_phone_number - otp_record = OTPCode.objects.filter(phone_number=full_phone, code=code, is_used=False).last() + if otp_method == 'email': + otp_record = OTPCode.objects.filter(email=user.email, code=code, is_used=False).last() + else: + full_phone = profile.full_phone_number + otp_record = OTPCode.objects.filter(phone_number=full_phone, code=code, is_used=False).last() if otp_record and otp_record.is_valid(): otp_record.is_used = True @@ -202,7 +237,7 @@ def verify_otp_login(request): else: form = OTPVerifyForm() - return render(request, 'registration/verify_otp.html', {'form': form, 'purpose': 'login'}) + return render(request, 'registration/verify_otp.html', {'form': form, 'purpose': 'login', 'otp_method': otp_method}) @login_required def dashboard(request):