editing email

This commit is contained in:
Flatlogic Bot 2026-01-25 16:35:29 +00:00
parent a8a48697b3
commit d8387d341e
13 changed files with 193 additions and 50 deletions

View File

@ -213,4 +213,14 @@ WHATSAPP_ENABLED = os.getenv("WHATSAPP_ENABLED", "true").lower() == "true"
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_URL = 'login' LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'dashboard' LOGIN_REDIRECT_URL = 'dashboard'
LOGOUT_REDIRECT_URL = 'index' 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"

View File

@ -10,6 +10,7 @@ from django.contrib import messages
from .whatsapp_utils import send_whatsapp_message_detailed from .whatsapp_utils import send_whatsapp_message_detailed
from django.core.mail import send_mail from django.core.mail import send_mail
from django.conf import settings from django.conf import settings
from .mail import send_html_email
import logging import logging
class ProfileInline(admin.StackedInline): class ProfileInline(admin.StackedInline):
@ -81,12 +82,12 @@ class PlatformProfileAdmin(admin.ModelAdmin):
email = request.POST.get('email') email = request.POST.get('email')
if email: if email:
try: try:
send_mail( send_html_email(
subject="Test Email from Platform", subject="Test Email from Platform",
message="This is a test email to verify your platform's email configuration.", message="This is a test email to verify your platform's email configuration. If you see the logo and nice formatting, it works!",
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[email], recipient_list=[email],
fail_silently=False, title="Test Email",
request=request
) )
messages.success(request, f"Success: Test email sent to {email}.") messages.success(request, f"Success: Test email sent to {email}.")
except Exception as e: except Exception as e:
@ -129,4 +130,4 @@ admin.site.register(Parcel, ParcelAdmin)
admin.site.register(Country) admin.site.register(Country)
admin.site.register(Governate) admin.site.register(Governate)
admin.site.register(City) admin.site.register(City)
admin.site.register(PlatformProfile, PlatformProfileAdmin) admin.site.register(PlatformProfile, PlatformProfileAdmin)

View File

@ -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 from django.conf import settings
import logging import logging
logger = logging.getLogger(__name__) 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): def send_contact_message(name, email, message):
""" """
Sends a contact form message to the platform admins. 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] 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, subject=subject,
message=full_message, message=full_message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=recipient_list, recipient_list=recipient_list,
fail_silently=False, title="New Contact Message"
) )
return True
except Exception as e: except Exception as e:
logger.error(f"Failed to send contact message: {e}") logger.error(f"Failed to send contact message: {e}")
return False return False

View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background-color: #ffffff;
padding: 20px;
text-align: center;
border-bottom: 3px solid #0d6efd; /* Bootstrap primary blue */
}
.header img {
max-height: 80px;
width: auto;
}
.content {
padding: 30px;
color: #333333;
line-height: 1.6;
}
.footer {
background-color: #f8f9fa;
padding: 20px;
text-align: center;
font-size: 12px;
color: #888888;
}
.button {
display: inline-block;
padding: 10px 20px;
background-color: #0d6efd;
color: #ffffff !important;
text-decoration: none;
border-radius: 5px;
margin-top: 20px;
}
h1, h2, h3 {
color: #2c3e50;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
{% if platform.logo %}
<img src="{{ site_url }}{{ platform.logo.url }}" alt="{{ platform.name }}">
{% else %}
<h1>{{ platform.name }}</h1>
{% endif %}
</div>
<div class="content">
{% if title %}
<h2>{{ title }}</h2>
{% endif %}
{{ message|safe|linebreaks }}
{% if action_url %}
<div style="text-align: center;">
<a href="{{ action_url }}" class="button">{{ action_text|default:"Click here" }}</a>
</div>
{% endif %}
</div>
<div class="footer">
<p>&copy; {% now "Y" %} {{ platform.name }}. All rights reserved.</p>
{% if platform.address %}
<p>{{ platform.address }}</p>
{% endif %}
</div>
</div>
</body>
</html>

View File

@ -8,6 +8,7 @@ urlpatterns = [
path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'), path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
path('register/', views.register, name='register'), path('register/', views.register, name='register'),
path('register/verify/', views.verify_registration, name='verify_registration'), path('register/verify/', views.verify_registration, name='verify_registration'),
path('dashboard/', views.dashboard, name='dashboard'), path('dashboard/', views.dashboard, name='dashboard'),
path('shipment-request/', views.shipment_request, name='shipment_request'), path('shipment-request/', views.shipment_request, name='shipment_request'),
path('accept-parcel/<int:parcel_id>/', views.accept_parcel, name='accept_parcel'), path('accept-parcel/<int:parcel_id>/', views.accept_parcel, name='accept_parcel'),
@ -21,9 +22,9 @@ urlpatterns = [
path('ajax/get-cities/', views.get_cities, name='get_cities'), path('ajax/get-cities/', views.get_cities, name='get_cities'),
path('privacy-policy/', views.privacy_policy, name='privacy_policy'), path('privacy-policy/', views.privacy_policy, name='privacy_policy'),
path('terms-conditions/', views.terms_conditions, name='terms_conditions'), 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/', 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'), path('profile/verify-otp/', views.verify_otp_view, name='verify_otp'),
] ]

View File

@ -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 import login, authenticate, logout
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.decorators import login_required 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 .models import Parcel, Profile, Country, Governate, City, OTPVerification, PlatformProfile
from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -21,7 +22,7 @@ from .whatsapp_utils import (
notify_status_change, notify_status_change,
send_whatsapp_message send_whatsapp_message
) )
from .mail import send_contact_message from .mail import send_contact_message, send_html_email
def index(request): def index(request):
tracking_id = request.GET.get('tracking_id') tracking_id = request.GET.get('tracking_id')
@ -59,12 +60,12 @@ def register(request):
send_whatsapp_message(phone, f"Your verification code is: {code}") send_whatsapp_message(phone, f"Your verification code is: {code}")
messages.info(request, _("Verification code sent to WhatsApp.")) messages.info(request, _("Verification code sent to WhatsApp."))
else: else:
send_mail( send_html_email(
_('Verification Code'), subject=_('Verification Code'),
f'Your verification code is: {code}', message=f'Your verification code is: {code}',
settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email],
[user.email], title=_('Welcome to Masar!'),
fail_silently=False, request=request
) )
messages.info(request, _("Verification code sent to email.")) messages.info(request, _("Verification code sent to email."))
@ -117,7 +118,7 @@ def verify_registration(request):
def dashboard(request): def dashboard(request):
# Ensure profile exists # Ensure profile exists
profile, created = Profile.objects.get_or_create(user=request.user) profile, created = Profile.objects.get_or_create(user=request.user)
if profile.role == 'shipper': if profile.role == 'shipper':
parcels = Parcel.objects.filter(shipper=request.user).order_by('-created_at') parcels = Parcel.objects.filter(shipper=request.user).order_by('-created_at')
return render(request, 'core/shipper_dashboard.html', {'parcels': parcels}) return render(request, 'core/shipper_dashboard.html', {'parcels': parcels})
@ -136,7 +137,7 @@ def shipment_request(request):
if profile.role != 'shipper': if profile.role != 'shipper':
messages.error(request, _("Only shippers can request shipments.")) messages.error(request, _("Only shippers can request shipments."))
return redirect('dashboard') return redirect('dashboard')
if request.method == 'POST': if request.method == 'POST':
form = ParcelForm(request.POST) form = ParcelForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -159,7 +160,7 @@ def accept_parcel(request, parcel_id):
if profile.role != 'car_owner': if profile.role != 'car_owner':
messages.error(request, _("Only car owners can accept shipments.")) messages.error(request, _("Only car owners can accept shipments."))
return redirect('dashboard') return redirect('dashboard')
parcel = get_object_or_404(Parcel, id=parcel_id, status='pending', payment_status='paid') parcel = get_object_or_404(Parcel, id=parcel_id, status='pending', payment_status='paid')
parcel.carrier = request.user parcel.carrier = request.user
parcel.status = 'picked_up' parcel.status = 'picked_up'
@ -230,7 +231,7 @@ def payment_success(request):
messages.success(request, _("Payment successful! Your shipment is now active.")) messages.success(request, _("Payment successful! Your shipment is now active."))
else: else:
messages.warning(request, _("Payment status is pending or failed. Please check your dashboard.")) messages.warning(request, _("Payment status is pending or failed. Please check your dashboard."))
return redirect('dashboard') return redirect('dashboard')
@login_required @login_required
@ -263,7 +264,7 @@ def privacy_policy(request):
def terms_conditions(request): def terms_conditions(request):
return render(request, 'core/terms_conditions.html') return render(request, 'core/terms_conditions.html')
def contact_view(request): def contact(request):
if request.method == 'POST': if request.method == 'POST':
form = ContactForm(request.POST) form = ContactForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -287,7 +288,7 @@ def profile_view(request):
return render(request, 'core/profile.html', {'profile': request.user.profile}) return render(request, 'core/profile.html', {'profile': request.user.profile})
@login_required @login_required
def edit_profile_view(request): def edit_profile(request):
if request.method == 'POST': if request.method == 'POST':
form = UserProfileForm(request.POST, request.FILES, instance=request.user.profile) form = UserProfileForm(request.POST, request.FILES, instance=request.user.profile)
if form.is_valid(): if form.is_valid():
@ -310,11 +311,11 @@ def edit_profile_view(request):
'city_id': data['city'].id if data['city'] else None, 'city_id': data['city'].id if data['city'] else None,
} }
request.session['pending_profile_update'] = safe_data request.session['pending_profile_update'] = safe_data
# 3. Generate OTP # 3. Generate OTP
code = ''.join(random.choices(string.digits, k=6)) code = ''.join(random.choices(string.digits, k=6))
OTPVerification.objects.create(user=request.user, code=code, purpose='profile_update') OTPVerification.objects.create(user=request.user, code=code, purpose='profile_update')
# 4. Send OTP # 4. Send OTP
method = data.get('otp_method', 'email') method = data.get('otp_method', 'email')
if method == 'whatsapp': if method == 'whatsapp':
@ -326,19 +327,19 @@ def edit_profile_view(request):
# Default to email # Default to email
# Send to the NEW email address (from the form), not the old one # Send to the NEW email address (from the form), not the old one
target_email = data['email'] target_email = data['email']
send_mail( send_html_email(
_('Verification Code'), subject=_('Verification Code'),
f'Your verification code is: {code}', message=f'Your verification code is: {code}',
settings.DEFAULT_FROM_EMAIL, recipient_list=[target_email],
[target_email], title=_('Profile Update Verification'),
fail_silently=False, request=request
) )
messages.info(request, _("Verification code sent to email.")) messages.info(request, _("Verification code sent to email."))
return redirect('verify_otp') return redirect('verify_otp')
else: else:
form = UserProfileForm(instance=request.user.profile) form = UserProfileForm(instance=request.user.profile)
return render(request, 'core/edit_profile.html', {'form': form}) return render(request, 'core/edit_profile.html', {'form': form})
@login_required @login_required
@ -390,4 +391,4 @@ def verify_otp_view(request):
except OTPVerification.DoesNotExist: except OTPVerification.DoesNotExist:
messages.error(request, _("Invalid code.")) messages.error(request, _("Invalid code."))
return render(request, 'core/verify_otp.html') return render(request, 'core/verify_otp.html')

View File

@ -5,6 +5,7 @@ from django.conf import settings
from django.core.mail import send_mail from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .models import PlatformProfile from .models import PlatformProfile
from .mail import send_html_email
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -149,12 +150,11 @@ Please proceed to payment to make it visible to drivers."""
# Email # Email
if parcel.shipper.email: if parcel.shipper.email:
try: try:
send_mail( send_html_email(
subject='Shipment Request Received - ' + parcel.tracking_number, subject='Shipment Request Received - ' + parcel.tracking_number,
message=message, message=message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[parcel.shipper.email], recipient_list=[parcel.shipper.email],
fail_silently=False title='Shipment Request Received'
) )
logger.info(f"Shipment created email sent to {parcel.shipper.email}") logger.info(f"Shipment created email sent to {parcel.shipper.email}")
except Exception as e: except Exception as e:
@ -176,12 +176,11 @@ Your shipment is now visible to available drivers."""
# Email Shipper # Email Shipper
if parcel.shipper.email: if parcel.shipper.email:
try: try:
send_mail( send_html_email(
subject='Payment Successful - ' + parcel.tracking_number, subject='Payment Successful - ' + parcel.tracking_number,
message=shipper_msg, message=shipper_msg,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[parcel.shipper.email], recipient_list=[parcel.shipper.email],
fail_silently=False title='Payment Successful'
) )
except Exception as e: except Exception as e:
logger.error(f"Failed to send payment email to {parcel.shipper.email}: {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: if parcel.shipper.email:
try: try:
send_mail( send_html_email(
subject='Driver Assigned - ' + parcel.tracking_number, subject='Driver Assigned - ' + parcel.tracking_number,
message=msg, message=msg,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[parcel.shipper.email], recipient_list=[parcel.shipper.email],
fail_silently=True title='Driver Assigned'
) )
except Exception: except Exception:
pass pass
@ -227,12 +225,11 @@ New Status: {parcel.get_status_display()}"""
if parcel.shipper.email: if parcel.shipper.email:
try: try:
send_mail( send_html_email(
subject='Shipment Update - ' + parcel.tracking_number, subject='Shipment Update - ' + parcel.tracking_number,
message=msg, message=msg,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[parcel.shipper.email], recipient_list=[parcel.shipper.email],
fail_silently=True title='Shipment Update'
) )
except Exception: except Exception:
pass pass