diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc
index ccdc8c4..00e611d 100644
Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ
diff --git a/config/settings.py b/config/settings.py
index d104fa0..1db02a9 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -213,4 +213,14 @@ WHATSAPP_ENABLED = os.getenv("WHATSAPP_ENABLED", "true").lower() == "true"
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'dashboard'
-LOGOUT_REDIRECT_URL = 'index'
\ No newline at end of file
+LOGOUT_REDIRECT_URL = 'index'
+
+# Site URL for Emails
+HOST_FQDN = os.getenv("HOST_FQDN", "")
+if HOST_FQDN:
+ if not HOST_FQDN.startswith(("http://", "https://")):
+ SITE_URL = f"https://{HOST_FQDN}"
+ else:
+ SITE_URL = HOST_FQDN
+else:
+ SITE_URL = "http://127.0.0.1:8000"
diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc
index 3c03fa2..3418d8b 100644
Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ
diff --git a/core/__pycache__/mail.cpython-311.pyc b/core/__pycache__/mail.cpython-311.pyc
index cd23f61..a080a1b 100644
Binary files a/core/__pycache__/mail.cpython-311.pyc and b/core/__pycache__/mail.cpython-311.pyc differ
diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc
index 92cbcc8..78274b5 100644
Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ
diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc
index 3d1e861..e809853 100644
Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ
diff --git a/core/__pycache__/whatsapp_utils.cpython-311.pyc b/core/__pycache__/whatsapp_utils.cpython-311.pyc
index 44c265e..327d6f6 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 45b972e..29a95fc 100644
--- a/core/admin.py
+++ b/core/admin.py
@@ -10,6 +10,7 @@ from django.contrib import messages
from .whatsapp_utils import send_whatsapp_message_detailed
from django.core.mail import send_mail
from django.conf import settings
+from .mail import send_html_email
import logging
class ProfileInline(admin.StackedInline):
@@ -81,12 +82,12 @@ class PlatformProfileAdmin(admin.ModelAdmin):
email = request.POST.get('email')
if email:
try:
- send_mail(
+ send_html_email(
subject="Test Email from Platform",
- message="This is a test email to verify your platform's email configuration.",
- from_email=settings.DEFAULT_FROM_EMAIL,
+ message="This is a test email to verify your platform's email configuration. If you see the logo and nice formatting, it works!",
recipient_list=[email],
- fail_silently=False,
+ title="Test Email",
+ request=request
)
messages.success(request, f"Success: Test email sent to {email}.")
except Exception as e:
@@ -129,4 +130,4 @@ admin.site.register(Parcel, ParcelAdmin)
admin.site.register(Country)
admin.site.register(Governate)
admin.site.register(City)
-admin.site.register(PlatformProfile, PlatformProfileAdmin)
\ No newline at end of file
+admin.site.register(PlatformProfile, PlatformProfileAdmin)
diff --git a/core/mail.py b/core/mail.py
index b855004..a66e6a5 100644
--- a/core/mail.py
+++ b/core/mail.py
@@ -1,9 +1,57 @@
-from django.core.mail import send_mail
+from django.core.mail import send_mail, EmailMultiAlternatives
+from django.template.loader import render_to_string
+from django.utils.html import strip_tags
from django.conf import settings
import logging
logger = logging.getLogger(__name__)
+def send_html_email(subject, message, recipient_list, title=None, action_url=None, action_text=None, request=None):
+ """
+ Sends a styled HTML email using the platform template.
+ """
+ try:
+ from .models import PlatformProfile
+ platform = PlatformProfile.objects.first()
+ if not platform:
+ # Create a dummy platform object if none exists, to avoid errors
+ class DummyPlatform:
+ name = "Platform"
+ logo = None
+ address = ""
+ platform = DummyPlatform()
+
+ # Determine site URL
+ site_url = settings.SITE_URL if hasattr(settings, 'SITE_URL') else 'http://127.0.0.1:8000'
+ if request:
+ site_url = f"{request.scheme}://{request.get_host()}"
+
+ context = {
+ 'platform': platform,
+ 'title': title or subject,
+ 'message': message,
+ 'action_url': action_url,
+ 'action_text': action_text,
+ 'site_url': site_url,
+ }
+
+ html_content = render_to_string('emails/base_email.html', context)
+ text_content = strip_tags(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, settings.DEFAULT_FROM_EMAIL, recipient_list)
+ msg.attach_alternative(html_content, "text/html")
+ msg.send()
+ return True
+ except Exception as e:
+ logger.error(f"Failed to send HTML email: {e}")
+ # Fallback to plain text
+ try:
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, recipient_list)
+ return True
+ except Exception as e2:
+ logger.error(f"Failed to send fallback email: {e2}")
+ return False
+
def send_contact_message(name, email, message):
"""
Sends a contact form message to the platform admins.
@@ -25,14 +73,13 @@ def send_contact_message(name, email, message):
recipient_list = settings.CONTACT_EMAIL_TO or [settings.DEFAULT_FROM_EMAIL]
- send_mail(
+ # Use HTML email for contact form too, for consistency
+ return send_html_email(
subject=subject,
message=full_message,
- from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=recipient_list,
- fail_silently=False,
+ title="New Contact Message"
)
- return True
except Exception as e:
logger.error(f"Failed to send contact message: {e}")
- return False
+ return False
\ No newline at end of file
diff --git a/core/templates/emails/base_email.html b/core/templates/emails/base_email.html
new file mode 100644
index 0000000..5b02a65
--- /dev/null
+++ b/core/templates/emails/base_email.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+ {% if title %}
+
{{ title }}
+ {% endif %}
+
+ {{ message|safe|linebreaks }}
+
+ {% if action_url %}
+
+ {% endif %}
+
+
+
+
+
\ No newline at end of file
diff --git a/core/urls.py b/core/urls.py
index dc3c824..f5f57ce 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -8,6 +8,7 @@ urlpatterns = [
path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
path('register/', views.register, name='register'),
path('register/verify/', views.verify_registration, name='verify_registration'),
+
path('dashboard/', views.dashboard, name='dashboard'),
path('shipment-request/', views.shipment_request, name='shipment_request'),
path('accept-parcel//', views.accept_parcel, name='accept_parcel'),
@@ -21,9 +22,9 @@ urlpatterns = [
path('ajax/get-cities/', views.get_cities, name='get_cities'),
path('privacy-policy/', views.privacy_policy, name='privacy_policy'),
path('terms-conditions/', views.terms_conditions, name='terms_conditions'),
- path('contact/', views.contact_view, name='contact'),
+ path('contact/', views.contact, name='contact'),
path('profile/', views.profile_view, name='profile'),
- path('profile/edit/', views.edit_profile_view, name='edit_profile'),
+ path('profile/edit/', views.edit_profile, name='edit_profile'),
path('profile/verify-otp/', views.verify_otp_view, name='verify_otp'),
]
diff --git a/core/views.py b/core/views.py
index 3ac78ad..cdb8c84 100644
--- a/core/views.py
+++ b/core/views.py
@@ -2,6 +2,7 @@ from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
from .models import Parcel, Profile, Country, Governate, City, OTPVerification, PlatformProfile
from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm
from django.utils.translation import gettext_lazy as _
@@ -21,7 +22,7 @@ from .whatsapp_utils import (
notify_status_change,
send_whatsapp_message
)
-from .mail import send_contact_message
+from .mail import send_contact_message, send_html_email
def index(request):
tracking_id = request.GET.get('tracking_id')
@@ -59,12 +60,12 @@ def register(request):
send_whatsapp_message(phone, f"Your verification code is: {code}")
messages.info(request, _("Verification code sent to WhatsApp."))
else:
- send_mail(
- _('Verification Code'),
- f'Your verification code is: {code}',
- settings.DEFAULT_FROM_EMAIL,
- [user.email],
- fail_silently=False,
+ send_html_email(
+ subject=_('Verification Code'),
+ message=f'Your verification code is: {code}',
+ recipient_list=[user.email],
+ title=_('Welcome to Masar!'),
+ request=request
)
messages.info(request, _("Verification code sent to email."))
@@ -117,7 +118,7 @@ def verify_registration(request):
def dashboard(request):
# Ensure profile exists
profile, created = Profile.objects.get_or_create(user=request.user)
-
+
if profile.role == 'shipper':
parcels = Parcel.objects.filter(shipper=request.user).order_by('-created_at')
return render(request, 'core/shipper_dashboard.html', {'parcels': parcels})
@@ -136,7 +137,7 @@ def shipment_request(request):
if profile.role != 'shipper':
messages.error(request, _("Only shippers can request shipments."))
return redirect('dashboard')
-
+
if request.method == 'POST':
form = ParcelForm(request.POST)
if form.is_valid():
@@ -159,7 +160,7 @@ def accept_parcel(request, parcel_id):
if profile.role != 'car_owner':
messages.error(request, _("Only car owners can accept shipments."))
return redirect('dashboard')
-
+
parcel = get_object_or_404(Parcel, id=parcel_id, status='pending', payment_status='paid')
parcel.carrier = request.user
parcel.status = 'picked_up'
@@ -230,7 +231,7 @@ def payment_success(request):
messages.success(request, _("Payment successful! Your shipment is now active."))
else:
messages.warning(request, _("Payment status is pending or failed. Please check your dashboard."))
-
+
return redirect('dashboard')
@login_required
@@ -263,7 +264,7 @@ def privacy_policy(request):
def terms_conditions(request):
return render(request, 'core/terms_conditions.html')
-def contact_view(request):
+def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
@@ -287,7 +288,7 @@ def profile_view(request):
return render(request, 'core/profile.html', {'profile': request.user.profile})
@login_required
-def edit_profile_view(request):
+def edit_profile(request):
if request.method == 'POST':
form = UserProfileForm(request.POST, request.FILES, instance=request.user.profile)
if form.is_valid():
@@ -310,11 +311,11 @@ def edit_profile_view(request):
'city_id': data['city'].id if data['city'] else None,
}
request.session['pending_profile_update'] = safe_data
-
+
# 3. Generate OTP
code = ''.join(random.choices(string.digits, k=6))
OTPVerification.objects.create(user=request.user, code=code, purpose='profile_update')
-
+
# 4. Send OTP
method = data.get('otp_method', 'email')
if method == 'whatsapp':
@@ -326,19 +327,19 @@ def edit_profile_view(request):
# Default to email
# Send to the NEW email address (from the form), not the old one
target_email = data['email']
- send_mail(
- _('Verification Code'),
- f'Your verification code is: {code}',
- settings.DEFAULT_FROM_EMAIL,
- [target_email],
- fail_silently=False,
+ send_html_email(
+ subject=_('Verification Code'),
+ message=f'Your verification code is: {code}',
+ recipient_list=[target_email],
+ title=_('Profile Update Verification'),
+ request=request
)
messages.info(request, _("Verification code sent to email."))
-
+
return redirect('verify_otp')
else:
form = UserProfileForm(instance=request.user.profile)
-
+
return render(request, 'core/edit_profile.html', {'form': form})
@login_required
@@ -390,4 +391,4 @@ def verify_otp_view(request):
except OTPVerification.DoesNotExist:
messages.error(request, _("Invalid code."))
- return render(request, 'core/verify_otp.html')
\ No newline at end of file
+ return render(request, 'core/verify_otp.html')
diff --git a/core/whatsapp_utils.py b/core/whatsapp_utils.py
index 2b4de8f..0c14d41 100644
--- a/core/whatsapp_utils.py
+++ b/core/whatsapp_utils.py
@@ -5,6 +5,7 @@ from django.conf import settings
from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _
from .models import PlatformProfile
+from .mail import send_html_email
logger = logging.getLogger(__name__)
@@ -149,12 +150,11 @@ Please proceed to payment to make it visible to drivers."""
# Email
if parcel.shipper.email:
try:
- send_mail(
+ send_html_email(
subject='Shipment Request Received - ' + parcel.tracking_number,
message=message,
- from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[parcel.shipper.email],
- fail_silently=False
+ title='Shipment Request Received'
)
logger.info(f"Shipment created email sent to {parcel.shipper.email}")
except Exception as e:
@@ -176,12 +176,11 @@ Your shipment is now visible to available drivers."""
# Email Shipper
if parcel.shipper.email:
try:
- send_mail(
+ send_html_email(
subject='Payment Successful - ' + parcel.tracking_number,
message=shipper_msg,
- from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[parcel.shipper.email],
- fail_silently=False
+ title='Payment Successful'
)
except Exception as e:
logger.error(f"Failed to send payment email to {parcel.shipper.email}: {e}")
@@ -205,12 +204,11 @@ Status: {parcel.get_status_display()}"""
if parcel.shipper.email:
try:
- send_mail(
+ send_html_email(
subject='Driver Assigned - ' + parcel.tracking_number,
message=msg,
- from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[parcel.shipper.email],
- fail_silently=True
+ title='Driver Assigned'
)
except Exception:
pass
@@ -227,12 +225,11 @@ New Status: {parcel.get_status_display()}"""
if parcel.shipper.email:
try:
- send_mail(
+ send_html_email(
subject='Shipment Update - ' + parcel.tracking_number,
message=msg,
- from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[parcel.shipper.email],
- fail_silently=True
+ title='Shipment Update'
)
except Exception:
pass