diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index b2f91d2..83a696d 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 1ec0e42..8a0cf21 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/whatsapp_utils.cpython-311.pyc b/core/__pycache__/whatsapp_utils.cpython-311.pyc index b133c19..3a3a5af 100644 Binary files a/core/__pycache__/whatsapp_utils.cpython-311.pyc and b/core/__pycache__/whatsapp_utils.cpython-311.pyc differ 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 0000000..4629412 Binary files /dev/null and b/core/migrations/__pycache__/0010_platformprofile_whatsapp_access_token_and_more.cpython-311.pyc differ 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 0000000..47d2c80 Binary files /dev/null and b/media/profile_pics/white_mocha.jfif differ