From 1e836a1d9d6f79c5f08d5331784036945688fc6a Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 25 Jan 2026 12:05:49 +0000 Subject: [PATCH] adding whats config --- core/__pycache__/admin.cpython-311.pyc | Bin 3283 -> 3763 bytes core/__pycache__/models.cpython-311.pyc | Bin 15363 -> 15900 bytes .../whatsapp_utils.cpython-311.pyc | Bin 5434 -> 6958 bytes core/admin.py | 17 +++++- ...mprofile_whatsapp_access_token_and_more.py | 23 ++++++++ ...sapp_access_token_and_more.cpython-311.pyc | Bin 0 -> 1303 bytes core/models.py | 6 +- core/whatsapp_utils.py | 53 ++++++++++++++---- media/profile_pics/white_mocha.jfif | Bin 0 -> 7861 bytes 9 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 core/migrations/0010_platformprofile_whatsapp_access_token_and_more.py create mode 100644 core/migrations/__pycache__/0010_platformprofile_whatsapp_access_token_and_more.cpython-311.pyc create mode 100644 media/profile_pics/white_mocha.jfif diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index b2f91d2ed7ab8ddfe6594485c37b334295f88648..83a696d928dcafa43fa511758c9b5c27108057d9 100644 GIT binary patch delta 761 zcmZXQ&ubGw6vt3&pnVF=f zAjv_n_kap|_bdhf1;LYG!Lq0R30qX~=uFa9#5eGnH{UmJzP$N%X6IbtLq2Z<72mB6 zOR^sepE6EHpH=)=Rd@y701E1mfVx3o-6RGvNs2+wAZe9Z!z`n+>@dp_n~ah1_l9S9 zsd{P~c#z~@X6xx~@Eq2yhoD**{L0MtR$|Lz)F+~9^6SbbPWfRYj4h!y9Bh^Dgn`7a zM76*Rc?(Mx2GvxY!Gv%s1iz?S_zm4>cd&GVc58$3IK508*smIKA>yoq-L4abK6AVA zgrvMB95)OI(@%-sR{3!gOR*3|NLhKT(dHu)Bc9yU6&ytlc3oxW$nY5r;>xkS(H1Pw zg{T<@^w8X4Wb5hOKpCNK*yiW~l^7j+EFQL?h1D8zIb9+&kPQ0*H7TbvXst;FMVrj` zQP`oJGeVJUQY5-U(iS4r!);%RnX18W>Z!*F6)tCy9%koC)`W0ra5Z-s&JCXA?)5T! zLjO-uW`kZ$h*T+J*)b|~y3i*ms1ys1rPb>2IBTVbUJB8nUaJ2SI zi~Z&OTz+r-)YtNCVj2^60*u^PVuAbi`Jwtqsg+~ZE)K<8N-X^qr}xThds8oxw0S{^ zBdA9v3q`A6OVHqiT^XFU?ZK@58m{krw!gr|GQ5_6|M7Nick0rCF8tIuFo99og(xxp Jxys9Gj6Yon;?@8F delta 277 zcmdlids&iiIWI340}%LrE6rTOwvlfeyCD;Z4+Ni;fJ8gPbcR%hD8>|qD5ey~DCQ1^ zG{zLB7LF*E4u%TGDAr&GP3FnU92T3Ga56J8I!s={rNgK;`6`!t0$Y&)P=Apih!6r1 z!XUx{NZjI$k5A4?EG|w?&B=+6*JLbm28tFl0ttsAaUfY_0V6<8D7FO>KN=Wd@PV*X zgU<(+$?v%pC;#E*nk>LGjnQ-Sa-LI+PV9`v9~dx+8KzgHwLgL+zJQ32%nVG5R~QvO MFrg?d5(C-;0NG4H5dZ)H diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 1ec0e424701f28ef44cc7bd8abee4550bc5366c6..8a0cf213dc2e97a26cf3bfdffeecfa89bb87b3cb 100644 GIT binary patch delta 737 zcmZXQPiPZC6vk(!X^=n?ViIj@7p7pPfh5|dki>e4)rKA{iPj2L*>18M5;U9bX5*hA z=3vhvj0X>%(^!irfl_aJQ4b1R2(%OrUc4umyT0j$Xz(%b8}|3!_x9~RdGPX)Yt!j; zAc&9ldjC+v-S>lmTekXFccf|Oy|H%Pbr^&#kjv|`o6 z)`?dC{k)T1l7mhg7nf4BkX@dZ|dSD27BY zR>}nx+~EMxufl8ery$jl{_Iq~q$+f&Qp_kiUA$BcF6R{rF!C?8=Jh>^)FLe_Mnww; zhIBU{YE5QKwh&89$M7brPH*8YHaT+{``M3SAFIt6=MVk&`4>r=CR)piN6Q{g^l_%Q ztf=v=MD=JZu^=qhsn$-c(EgT9Ij f_-FHT{2LDX@N66XFVAw%?{+xv{w)UChq?Jb`JC8O delta 278 zcmbPJ(_F!~oR^o20SJs4%Q6?pZ{$kW*fc#jA|*uEeuhDObqS}DIzTl zDWc1m7#LOqF$6>jrHBPHXo^q%Zm?}~AdA-I9wQ6R#Xz|tMIbTRkk4hZpm7}IvdIO; zu8hKyAFzr{K4n}oS(Z&o-~dP!h?oJy#f^-c7nwLSZmu*t%EY*Jvc2U7#%q%U`GvX8 zfs}!WRg-zGt}~vS{M71>JXlZ%BqIYNB!R>)4x8Nkl+v73yQ0UNA6TDd)Dd7b`M`il YOh}uO^ARNe1tF<2nbA&c@_Sod03KFLQ~&?~ diff --git a/core/__pycache__/whatsapp_utils.cpython-311.pyc b/core/__pycache__/whatsapp_utils.cpython-311.pyc index b133c19f37d54aa28c81b8309c3910bd69cf1beb..3a3a5af095d281ef765888bd46670f036a25d295 100644 GIT binary patch delta 3036 zcmb_eT}&L;6}~gGJ3sq3uq-TQF_BZ}Slj%haxezBZd|w9WoH%^FSF}A z0|c~dkkUxBiWJi;IgVpjRH~e`j`EO7RU0W$6Qxp~s$FZ^ps8r{l1h1_iXvHk>bbK& zV4R0O^lIkJz2}~L=bZ1J@65b^=-uahZ+X2g1no0^A@vvky04MJ>z%C;g;N}9$DH%3 zlR+#}nOUA1(WVLy7=#|eZMXFs4J+bL(N&Kkf%GaakUoW(L=pFw)L<;aSfZ{PMoOF3 zzoe@`A&;lyMlyrv#&IT@N~;NbtFmyylY{%K7O;LD8N23YD{n>lU3$_h`GOKMsxBfl z>nN`xn$$6sXLBrW&apc|U}5(tk6IM|nm&-iC~t$}B?d2zTZ=!@r~1aFH8Q zU`1ha+?(h=`vg9SK`$bO&2u+7O)NbZnML#x_aa(kawx|nS-^^0&bz|UCR78b)CE-! zUzv#;dNi92C$Orhnvsg9^>7kr=E8fh3wLF)np81fQI$wIgTocDdn!B5V@(em^Vzg| zGTarxNaoNTZ2ov$1$0q3U|f$Xr)7tF@}0n%7er zohW&w&CgA#7@V`Dp_dYB)<|VEi%(~!r&VkTi*c+$HboqkP>#kD%hC81OURaT%3@TV zW&!z-?_X)0R*hKoZMz`F^^S8vu zkGI?b(7}GY<(BAs&G&O(Avp2r4G?e@#Yt0~EIf5RhPKVo^|ZqyPu;E2g1fokXx4+^ z{@v5jAo?iiIxBJ?weCO5aUXL|=qK3qjR5;McK!F?Vj^7M)BTrnd5VU;(JGa06sme`jr=R;)VxYI>_gxoH*+x zciCRXK_0OkjE{O6Q0l0MH}|w8l~Q--7(9#$W5-( z>$>SufSgK09(v?G?seuU`L3g_COoyYL$cxJ7sw4qM`ySzF{37~1B3SRGp;FB^9Z@^ z_!ZMc`kXHxuBkF7dU-NBJ{}tyi=MqObRPS_6U&j(fN>NfOpk!b{9&e*^z%Uh(;y>m z66b?0nARmsUANqcv>Mk+1hTk!oW^1DD&KJ$*TJk#0j33}+;D&{sn=Ca*`dW85HJo5 zI{|YPG<}0K3!&(6Q5-SFk%Bm~DF@c(zi2#KY<$LSe5UBAUmaQd!RM3(th4@3m;IfvxkP-xIec!hq}l=#2|Z=A#cc+$f$JPuka5bhabXSYWOI5OA0&i0i%xGmrnDF z0992af0zC$Vp^$(BXEMQ%LA4$6W8O0fo(1BfhjxQ2xsxxI8LZ(%cIYvvRNoUT6|8$ zN5Dd-B@U3o0W#>`?>Y;k3sn6FGVNX-7Ui{tS8v`~Dhd%(h)`lrGH)l}(afInX3wS4 zcuQ{I`2MedY<8S7J4Qe4EXot6JW&uQHr+LAC%%Hc5c#Lrz-lzWpOAS^vtQw!3h)(= z9iR*W2FWix-}<&Ahh}N*vKwT)GsjQ{f|fd5(F!@VApu3&1NTMvZtWs87`jNv8*+*O zmPAf@Z}Su@+)EyLql10WrS<(gRCSVyQ&jX*F+hcFTSd_FVl|xQ_3^b)4A}_uon0 z)6ACRW=r%>r;6f5Q@mIZFOph0&731Q<$dfZ#q_rgieoRH5Is*GO3kIDIb72(Q>K)g z4b8=xCQ)$$HvIDQq~Cjxd{A>`#10#9v4lh%!^ax$#pVobQ3A+D?n#vJFw90cJV-9p zwlD#bt!;LVfhrBR{vNqq`=f6TeRNOgqkBSswn|ds_BVyj!FGx2&k_u(KUI&(p#O*8 zlwGg+?dbLNC>3p4>=tGgp>T+uML$U5KFo6Kg7%8LGS^;079pfwW)Ns zuE{kHg9WPn8Tqj8`2Qnxk%k_T@gdS3IH2-#8AVO&rN0shegZC({vpJe&F)ytF1DpI zOUoXn+_rNO(^J9JzuzEC_q0pqf5g+U<9u2@gRg*&A`GYY%Ru+aCTc4@d3`Q5tp!b~ z=}uo!YB#0!ReswMVD@jLs_5=!>bFr;?KY~4<{D;T8&yTS$UL`=sv;oJVMWM0 IUEACL0!i5Xr~m)} delta 1874 zcmaJ>Yitx%6uz@FJCA*K+4s^Q+Xt`(Dhj2wTl%1skQ5h8Ne~EGmYt#9ru*Q|6s7J8 zMvMd#usIq@D5fTgF@TTwh#Dg#$}bbMo6HX#HK{-N;lCyPF@_lLoqg1z;biWaJLfg$ zeCM9~;r6e)T<<%bHbm&>A7(Qi*Sssl9GhPsjaiVwOmkF0NwHpI5V}rQjSllNdk!5I zWbWMj$v}{qKr!(Ts!GHdLsS(_%j70i_=pQwICg$10>5+dNZpsdmLp?_h7d$O-WIi0 zYyJPewea`7y(|g`rAFr?%Jy5{j`}p{notjm9>|*$kuwqGgpwzUu8?IED%YP zmj8VPUA4%fEG<|{oNOyvWV`GrG8sm8E^sBQgtTDO2~9g1`yyN9idNZm!+nk9d%c-+ zi7)bt&W06v!>^7YUlNM^vw4Ntr43t>r>`iGY`B#uGf1{fpmwCK zZCE1IfpAYrT!EAs(e2l~%T`4ZQo?$t_@YQ7$bk~s!JEQcOb2Wh+qv%DbL-%k*aHI8(b!LA`iDMosuCum%=&H zVE9|wyx&dT?5dX1X4PamFDr&s!8nf%Y3%v5QqVH_Tud??g{gc_N#HUr!@r@z$eYlhGJIFRR#NM%WjrFs4`mJQ2 zZVm*=isnl(yuXCtmg5^Z zTYK7wOr`#$VjQCe!CYR;oH(6Sr!s}ClGBoDtfVwW#=S(lm6~6K3+}nCj_6|G-8Fi& zSC8(xJz23QbbF%ACGHao^#2*QL8_$$4_bUM=<#l3i)fm{Y7#k%T#+H%N~AQMhLkU& zMG-2FHh9}3JVA@C6R~T471?VRG81Igh@4=7-VJkcfA}n*`h~weEC9!8`rtp0oj6Vu zF&6j0$-o*W2CoNNZKFiAk1X{y_#$xhq2$#HH>z`^Wo{IH3`dwg$OgmA4R|$p1iC}( z-B#jPCz2lz{hNv|+xh}?EMNRfBV`KI) dRrD_&axp_y)MRUUW_uMiS&Nl^{+F5Q`~xf;utxv@ diff --git a/core/admin.py b/core/admin.py index ca26a2f..c987a18 100644 --- a/core/admin.py +++ b/core/admin.py @@ -34,9 +34,24 @@ class ParcelAdmin(admin.ModelAdmin): @admin.register(PlatformProfile) class PlatformProfileAdmin(admin.ModelAdmin): list_display = ('name', 'phone_number', 'registration_number') + fieldsets = ( + (None, { + 'fields': ('name', 'logo', 'slogan') + }), + ('Contact Information', { + 'fields': ('address', 'phone_number', 'registration_number', 'vat_number') + }), + ('Legal', { + 'fields': ('privacy_policy', 'terms_conditions') + }), + ('WhatsApp Configuration', { + 'fields': ('whatsapp_access_token', 'whatsapp_business_phone_number_id'), + 'description': 'Enter your Meta WhatsApp Business API credentials here. These will override the system defaults.' + }), + ) def has_add_permission(self, request): # Allow adding only if no instance exists if self.model.objects.exists(): return False - return super().has_add_permission(request) + return super().has_add_permission(request) \ No newline at end of file diff --git a/core/migrations/0010_platformprofile_whatsapp_access_token_and_more.py b/core/migrations/0010_platformprofile_whatsapp_access_token_and_more.py new file mode 100644 index 0000000..c87a808 --- /dev/null +++ b/core/migrations/0010_platformprofile_whatsapp_access_token_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.7 on 2026-01-25 12:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_profile_address_profile_profile_picture_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='platformprofile', + name='whatsapp_access_token', + field=models.TextField(blank=True, help_text='Permanent or temporary access token from Meta Business.', verbose_name='WhatsApp Access Token'), + ), + migrations.AddField( + model_name='platformprofile', + name='whatsapp_business_phone_number_id', + field=models.CharField(blank=True, help_text='The Phone Number ID from WhatsApp API setup.', max_length=100, verbose_name='WhatsApp Phone Number ID'), + ), + ] diff --git a/core/migrations/__pycache__/0010_platformprofile_whatsapp_access_token_and_more.cpython-311.pyc b/core/migrations/__pycache__/0010_platformprofile_whatsapp_access_token_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46294128d180815a8ff7bde2e7329a4152f9bde3 GIT binary patch literal 1303 zcmZuvOHUI)9G~58m+sPvAVd?Dg z@Zgaf-vBj|_#ylP95|4T3mE|Z3ZZwh zUFLk2Gy4Dota!jzs*0~xRRySE5n%Nzz!{=6d2Ht#aPC)Ms%EhE0Ti>R!dO(9>AQ7` z5;qKDru!i#UhH%OKFVn~$SH53yMT1=%cX)9l%vkAF#Y@zjkP`N)QmM?q zh=w)SBM@OsNgVg(o<4G&B&FOQVCajmk;p?yEu?Kv zjZhy2BuK1~S_$!^kRrNcb-XO;Wz}ftTPq|%)_fYf0S_;=CO!$kxhS&cB%rk}0E-!x zwLKJUFrz`d2qt7ZVfjr$?Jy=VKtACfrW}}*m?m^ATXTtr*^_>fcK03_HNt=groK%m zbaCtXdV^SNLTRl^rM2{~liyW7YfDy4k~AuvV&3xBgT7@TuBcJ5Cp6CqPYP{Aj|BCk zK?_{5S~OV>$|NCVCggp^DIWo3c?iFx$PoM65Cu^&bY`sc zCP9GEoh_wf3l`s_L1f7m~JzG{ZatoLmSp0P^xWNc||;;sA>( zk~p@+u~mU%tAjX&;W2%8Sv>SnuY%R0#ths|y;I|)aFf1|Id_?x?ac5E swYaG8FaF3$JEJHHuUjb|f$7_CTOU>QHn>gp%;URH&xGOPKUR6~KL=!LQ~&?~ literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 13e3e99..b7f2233 100644 --- a/core/models.py +++ b/core/models.py @@ -159,6 +159,10 @@ class PlatformProfile(models.Model): vat_number = models.CharField(_('VAT Number'), max_length=100, blank=True) privacy_policy = models.TextField(_('Privacy Policy'), blank=True) terms_conditions = models.TextField(_('Terms and Conditions'), blank=True) + + # WhatsApp Configuration + whatsapp_access_token = models.TextField(_('WhatsApp Access Token'), blank=True, help_text=_("Permanent or temporary access token from Meta Business.")) + whatsapp_business_phone_number_id = models.CharField(_('WhatsApp Phone Number ID'), max_length=100, blank=True, help_text=_("The Phone Number ID from WhatsApp API setup.")) def __str__(self): return self.name @@ -180,4 +184,4 @@ class OTPVerification(models.Model): def is_valid(self): # OTP valid for 10 minutes - return self.created_at >= timezone.now() - timezone.timedelta(minutes=10) + return self.created_at >= timezone.now() - timezone.timedelta(minutes=10) \ No newline at end of file diff --git a/core/whatsapp_utils.py b/core/whatsapp_utils.py index d89b48e..8c46bec 100644 --- a/core/whatsapp_utils.py +++ b/core/whatsapp_utils.py @@ -1,29 +1,54 @@ import requests import logging from django.conf import settings +from .models import PlatformProfile logger = logging.getLogger(__name__) +def get_whatsapp_credentials(): + """ + Retrieves WhatsApp credentials from PlatformProfile (preferred) or settings. + Returns tuple: (api_key, phone_id) + """ + # Default to settings + api_key = settings.WHATSAPP_API_KEY + phone_id = settings.WHATSAPP_PHONE_ID + + # Try to fetch from PlatformProfile + try: + profile = PlatformProfile.objects.first() + if profile: + if profile.whatsapp_access_token: + api_key = profile.whatsapp_access_token + if profile.whatsapp_business_phone_number_id: + phone_id = profile.whatsapp_business_phone_number_id + except Exception as e: + logger.warning(f"Failed to fetch PlatformProfile for WhatsApp config: {e}") + + return api_key, phone_id + def send_whatsapp_message(phone_number, message): """ Sends a WhatsApp message using the configured gateway. This implementation assumes Meta WhatsApp Business API (Graph API). """ if not settings.WHATSAPP_ENABLED: - logger.info("WhatsApp notifications are disabled.") + logger.info("WhatsApp notifications are disabled by settings.") return False - if not settings.WHATSAPP_API_KEY or not settings.WHATSAPP_PHONE_ID: - logger.warning("WhatsApp API configuration is missing.") + api_key, phone_id = get_whatsapp_credentials() + + if not api_key or not phone_id: + logger.warning("WhatsApp API configuration is missing (checked PlatformProfile and settings).") return False # Normalize phone number (ensure it has country code and no +) clean_phone = "".join(filter(str.isdigit, str(phone_number))) - url = f"https://graph.facebook.com/v17.0/{settings.WHATSAPP_PHONE_ID}/messages" + url = f"https://graph.facebook.com/v17.0/{phone_id}/messages" headers = { - "Authorization": f"Bearer {settings.WHATSAPP_API_KEY}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } @@ -58,7 +83,9 @@ Tracking Number: {parcel.tracking_number} Status: {parcel.get_status_display()} Please proceed to payment to make it visible to drivers.""" - return send_whatsapp_message(parcel.shipper.profile.phone_number, message) + if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number: + return send_whatsapp_message(parcel.shipper.profile.phone_number, message) + return False def notify_payment_received(parcel): """Notifies the shipper and receiver about successful payment.""" @@ -66,7 +93,9 @@ def notify_payment_received(parcel): shipper_name = parcel.shipper.get_full_name() or parcel.shipper.username shipper_msg = f"""Payment successful for shipment {parcel.tracking_number}. Your shipment is now visible to available drivers.""" - send_whatsapp_message(parcel.shipper.profile.phone_number, shipper_msg) + + if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number: + send_whatsapp_message(parcel.shipper.profile.phone_number, shipper_msg) # Notify Receiver receiver_msg = f"""Hello {parcel.receiver_name}, @@ -81,12 +110,16 @@ def notify_driver_assigned(parcel): driver_name = parcel.carrier.get_full_name() or parcel.carrier.username msg = f"""Shipment {parcel.tracking_number} has been picked up by {driver_name}. Status: {parcel.get_status_display()}""" - send_whatsapp_message(parcel.shipper.profile.phone_number, msg) + + if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number: + send_whatsapp_message(parcel.shipper.profile.phone_number, msg) send_whatsapp_message(parcel.receiver_phone, msg) def notify_status_change(parcel): """Notifies parties about general status updates (In Transit, Delivered).""" msg = f"""Update for shipment {parcel.tracking_number}: New Status: {parcel.get_status_display()}""" - send_whatsapp_message(parcel.shipper.profile.phone_number, msg) - send_whatsapp_message(parcel.receiver_phone, msg) + + if hasattr(parcel.shipper, 'profile') and parcel.shipper.profile.phone_number: + send_whatsapp_message(parcel.shipper.profile.phone_number, msg) + send_whatsapp_message(parcel.receiver_phone, msg) \ No newline at end of file diff --git a/media/profile_pics/white_mocha.jfif b/media/profile_pics/white_mocha.jfif new file mode 100644 index 0000000000000000000000000000000000000000..47d2c8078fdc4e8a1315fd1ef2075a37330f0112 GIT binary patch literal 7861 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