editing email
This commit is contained in:
parent
a8a48697b3
commit
d8387d341e
Binary file not shown.
@ -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'
|
||||
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"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
admin.site.register(PlatformProfile, PlatformProfileAdmin)
|
||||
|
||||
59
core/mail.py
59
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
|
||||
86
core/templates/emails/base_email.html
Normal file
86
core/templates/emails/base_email.html
Normal 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>© {% now "Y" %} {{ platform.name }}. All rights reserved.</p>
|
||||
{% if platform.address %}
|
||||
<p>{{ platform.address }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -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/<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('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'),
|
||||
]
|
||||
|
||||
@ -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')
|
||||
return render(request, 'core/verify_otp.html')
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user