331 lines
14 KiB
Python
331 lines
14 KiB
Python
from django.contrib import admin
|
|
from django.contrib.auth.admin import UserAdmin
|
|
from django.contrib.auth.models import User
|
|
from .models import Profile, Parcel, Country, Governate, City, PlatformProfile, Testimonial, DriverRating, NotificationTemplate, PricingRule
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.urls import path, reverse
|
|
from django.shortcuts import render
|
|
from django.utils.html import format_html
|
|
from django.contrib import messages
|
|
from .whatsapp_utils import send_whatsapp_message_detailed
|
|
from django.conf import settings
|
|
from .mail import send_html_email
|
|
import logging
|
|
import csv
|
|
from django.http import HttpResponse, HttpResponseRedirect
|
|
from rangefilter.filters import DateRangeFilter
|
|
from django.template.loader import render_to_string
|
|
import weasyprint
|
|
|
|
class ProfileInline(admin.StackedInline):
|
|
model = Profile
|
|
can_delete = False
|
|
verbose_name_plural = _('Profiles')
|
|
fieldsets = (
|
|
(None, {'fields': ('role', 'is_approved', 'phone_number', 'profile_picture', 'address')}),
|
|
(_('Driver Info'), {'fields': ('license_front_image', 'license_back_image', 'car_plate_number'), 'classes': ('collapse',)}),
|
|
(_('Location'), {'fields': ('country', 'governate', 'city'), 'classes': ('collapse',)}),
|
|
)
|
|
|
|
class CustomUserAdmin(UserAdmin):
|
|
inlines = (ProfileInline,)
|
|
list_display = ('username', 'email', 'get_role', 'get_approval_status', 'is_active', 'is_staff', 'send_whatsapp_link')
|
|
list_filter = ('is_active', 'is_staff', 'profile__role', 'profile__is_approved')
|
|
|
|
def get_role(self, obj):
|
|
return obj.profile.get_role_display()
|
|
get_role.short_description = _('Role')
|
|
|
|
def get_approval_status(self, obj):
|
|
return obj.profile.is_approved
|
|
get_approval_status.short_description = _('Approved')
|
|
get_approval_status.boolean = True
|
|
|
|
def get_inline_instances(self, request, obj=None):
|
|
if not obj:
|
|
return list()
|
|
return super(CustomUserAdmin, self).get_inline_instances(request, obj)
|
|
|
|
def get_urls(self):
|
|
urls = super().get_urls()
|
|
custom_urls = [
|
|
path('<int:user_id>/send-whatsapp/', self.admin_site.admin_view(self.send_whatsapp_view), name='user-send-whatsapp'),
|
|
]
|
|
return custom_urls + urls
|
|
|
|
def send_whatsapp_view(self, request, user_id):
|
|
user = self.get_object(request, user_id)
|
|
if not user:
|
|
messages.error(request, _("User not found."))
|
|
return HttpResponseRedirect(reverse('admin:auth_user_changelist'))
|
|
|
|
if not hasattr(user, 'profile') or not user.profile.phone_number:
|
|
messages.warning(request, _("This user does not have a phone number in their profile."))
|
|
return HttpResponseRedirect(reverse('admin:auth_user_changelist'))
|
|
|
|
if request.method == 'POST':
|
|
message = request.POST.get('message')
|
|
if message:
|
|
success, msg = send_whatsapp_message_detailed(user.profile.phone_number, message)
|
|
if success:
|
|
messages.success(request, _("WhatsApp message sent successfully."))
|
|
return HttpResponseRedirect(reverse('admin:auth_user_changelist'))
|
|
else:
|
|
messages.error(request, _(f"Failed to send message: {msg}"))
|
|
else:
|
|
messages.warning(request, _("Message cannot be empty."))
|
|
|
|
context = dict(
|
|
self.admin_site.each_context(request),
|
|
user_obj=user,
|
|
phone_number=user.profile.phone_number,
|
|
)
|
|
return render(request, "admin/core/user/send_whatsapp_message.html", context)
|
|
|
|
def send_whatsapp_link(self, obj):
|
|
if hasattr(obj, 'profile') and obj.profile.phone_number:
|
|
return format_html(
|
|
'<a class="button" href="{}">{}</a>',
|
|
reverse('admin:user-send-whatsapp', args=[obj.pk]),
|
|
_('Send WhatsApp')
|
|
)
|
|
return "-"
|
|
send_whatsapp_link.short_description = _("WhatsApp")
|
|
send_whatsapp_link.allow_tags = True
|
|
|
|
class ParcelAdmin(admin.ModelAdmin):
|
|
list_display = ('tracking_number', 'shipper', 'carrier', 'price', 'driver_amount', 'platform_fee', 'distance_km', 'status', 'payment_status', 'created_at')
|
|
list_filter = (
|
|
'status',
|
|
'payment_status',
|
|
('created_at', DateRangeFilter),
|
|
)
|
|
search_fields = ('tracking_number', 'shipper__username', 'receiver_name', 'carrier__username')
|
|
actions = ['export_as_csv', 'print_parcels', 'export_pdf']
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('tracking_number', 'shipper', 'carrier', 'status', 'payment_status', 'thawani_session_id')
|
|
}),
|
|
(_('Description'), {
|
|
'fields': ('description', 'receiver_name', 'receiver_phone')
|
|
}),
|
|
(_('Trip & Pricing'), {
|
|
'fields': ('distance_km', 'weight', 'price', 'platform_fee_percentage', 'platform_fee', 'driver_amount'),
|
|
'description': _('Pricing is calculated based on Distance and Weight.')
|
|
}),
|
|
(_('Pickup Location'), {
|
|
'fields': ('pickup_country', 'pickup_governate', 'pickup_city', 'pickup_address', 'pickup_lat', 'pickup_lng')
|
|
}),
|
|
(_('Delivery Location'), {
|
|
'fields': ('delivery_country', 'delivery_governate', 'delivery_city', 'delivery_address', 'delivery_lat', 'delivery_lng')
|
|
}),
|
|
)
|
|
|
|
def export_as_csv(self, request, queryset):
|
|
response = HttpResponse(content_type='text/csv')
|
|
response['Content-Disposition'] = 'attachment; filename="parcels_report.csv"'
|
|
writer = csv.writer(response)
|
|
|
|
writer.writerow(['Tracking Number', 'Shipper', 'Carrier', 'Total Price (OMR)', 'Platform Fee (%)', 'Platform Charge (OMR)', 'Driver Amount (OMR)', 'Distance (km)', 'Weight (kg)', 'Status', 'Payment Status', 'Created At'])
|
|
|
|
for obj in queryset:
|
|
writer.writerow([
|
|
obj.tracking_number,
|
|
obj.shipper.username if obj.shipper else '',
|
|
obj.carrier.username if obj.carrier else '',
|
|
obj.price,
|
|
obj.platform_fee_percentage,
|
|
obj.platform_fee,
|
|
obj.driver_amount,
|
|
obj.distance_km,
|
|
obj.weight,
|
|
obj.get_status_display(),
|
|
obj.get_payment_status_display(),
|
|
obj.created_at
|
|
])
|
|
|
|
return response
|
|
export_as_csv.short_description = _("Export Selected to CSV")
|
|
|
|
def print_parcels(self, request, queryset):
|
|
return render(request, 'admin/core/parcel/parcel_list_print.html', {'parcels': queryset, 'is_pdf': False})
|
|
print_parcels.short_description = _("Print Selected Parcels")
|
|
|
|
def export_pdf(self, request, queryset):
|
|
html_string = render_to_string('admin/core/parcel/parcel_list_print.html', {'parcels': queryset, 'is_pdf': True})
|
|
html = weasyprint.HTML(string=html_string, base_url=request.build_absolute_uri())
|
|
result = html.write_pdf()
|
|
|
|
response = HttpResponse(content_type='application/pdf')
|
|
response['Content-Disposition'] = 'attachment; filename="parcels_list.pdf"'
|
|
response.write(result)
|
|
return response
|
|
export_pdf.short_description = _("Download Selected as PDF")
|
|
|
|
class PlatformProfileAdmin(admin.ModelAdmin):
|
|
fieldsets = (
|
|
(_('General Info'), {
|
|
'fields': ('name', 'logo', 'slogan', 'address', 'phone_number', 'registration_number', 'vat_number')
|
|
}),
|
|
(_('Financial Configuration'), {
|
|
'fields': ('platform_fee_percentage', 'enable_payment')
|
|
}),
|
|
(_('Testing / Development'), {
|
|
'fields': ('auto_mark_paid',),
|
|
'description': _('Enable this to automatically mark NEW parcels as "Paid" (useful for testing so drivers can see them immediately).')
|
|
}),
|
|
(_('Integrations'), {
|
|
'fields': ('google_maps_api_key',),
|
|
'description': _('API Keys for external services.')
|
|
}),
|
|
(_('Policies'), {
|
|
'fields': ('privacy_policy_en', 'privacy_policy_ar', 'terms_conditions_en', 'terms_conditions_ar')
|
|
}),
|
|
(_('WhatsApp Configuration (Wablas Gateway)'), {
|
|
'fields': ('whatsapp_access_token', 'whatsapp_app_secret', 'whatsapp_business_phone_number_id'),
|
|
'description': _('Configure your Wablas API connection. Use "Test WhatsApp Configuration" to verify.')
|
|
}),
|
|
)
|
|
|
|
def has_add_permission(self, request):
|
|
# Allow only one instance
|
|
if self.model.objects.exists():
|
|
return False
|
|
return super().has_add_permission(request)
|
|
|
|
def get_urls(self):
|
|
urls = super().get_urls()
|
|
custom_urls = [
|
|
path('test-whatsapp/', self.admin_site.admin_view(self.test_whatsapp_view), name='test-whatsapp'),
|
|
path('test-email/', self.admin_site.admin_view(self.test_email_view), name='test-email'),
|
|
]
|
|
return custom_urls + urls
|
|
|
|
def test_whatsapp_view(self, request):
|
|
phone_number = ''
|
|
if request.method == 'POST':
|
|
phone_number = request.POST.get('phone_number')
|
|
if phone_number:
|
|
success, msg = send_whatsapp_message_detailed(phone_number, "This is a test message from your Platform.")
|
|
if success:
|
|
messages.success(request, f"Success: {msg}")
|
|
else:
|
|
messages.error(request, f"Error: {msg}")
|
|
else:
|
|
messages.warning(request, "Please enter a phone number.")
|
|
|
|
context = dict(
|
|
self.admin_site.each_context(request),
|
|
phone_number=phone_number,
|
|
)
|
|
return render(request, "admin/core/platformprofile/test_whatsapp.html", context)
|
|
|
|
def test_email_view(self, request):
|
|
email = ''
|
|
if request.method == 'POST':
|
|
email = request.POST.get('email')
|
|
if email:
|
|
try:
|
|
send_html_email(
|
|
subject="Test Email from Platform",
|
|
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],
|
|
title="Test Email",
|
|
request=request
|
|
)
|
|
messages.success(request, f"Success: Test email sent to {email}.")
|
|
except Exception as e:
|
|
messages.error(request, f"Error sending email: {str(e)}")
|
|
else:
|
|
messages.warning(request, "Please enter an email address.")
|
|
|
|
context = dict(
|
|
self.admin_site.each_context(request),
|
|
email=email,
|
|
)
|
|
return render(request, "admin/core/platformprofile/test_email.html", context)
|
|
|
|
def test_connection_link(self, obj):
|
|
return format_html(
|
|
'<a class="button" href="{}" style="margin-right: 10px;">{}</a>'
|
|
'<a class="button" href="{}">{}</a>',
|
|
reverse('admin:test-whatsapp'),
|
|
_('Test WhatsApp'),
|
|
reverse('admin:test-email'),
|
|
_('Test Email')
|
|
)
|
|
test_connection_link.short_description = _("Actions")
|
|
test_connection_link.allow_tags = True
|
|
|
|
readonly_fields = ('test_connection_link',)
|
|
|
|
def get_fieldsets(self, request, obj=None):
|
|
fieldsets = super().get_fieldsets(request, obj)
|
|
# Add the test link to the first fieldset or a new one
|
|
if obj:
|
|
# Check if 'Tools' fieldset already exists to avoid duplication if called multiple times (though get_fieldsets is usually fresh)
|
|
# Easier: just append it.
|
|
fieldsets += ((_('Tools'), {'fields': ('test_connection_link',)}),)
|
|
return fieldsets
|
|
|
|
class PricingRuleAdmin(admin.ModelAdmin):
|
|
list_display = ('distance_range', 'weight_range', 'price')
|
|
list_filter = ('min_distance', 'min_weight')
|
|
search_fields = ('price',)
|
|
ordering = ('min_distance', 'min_weight')
|
|
|
|
def distance_range(self, obj):
|
|
return f"{obj.min_distance} - {obj.max_distance} km"
|
|
distance_range.short_description = _("Distance Range")
|
|
|
|
def weight_range(self, obj):
|
|
return f"{obj.min_weight} - {obj.max_weight} kg"
|
|
weight_range.short_description = _("Weight Range")
|
|
|
|
class CountryAdmin(admin.ModelAdmin):
|
|
list_display = ('name_en', 'name_ar', 'phone_code')
|
|
search_fields = ('name_en', 'name_ar', 'phone_code')
|
|
|
|
class TestimonialAdmin(admin.ModelAdmin):
|
|
list_display = ('name_en', 'role_en', 'is_active', 'created_at')
|
|
list_filter = ('is_active', 'created_at')
|
|
search_fields = ('name_en', 'name_ar', 'content_en', 'content_ar')
|
|
list_editable = ('is_active',)
|
|
|
|
admin.site.unregister(User)
|
|
admin.site.register(User, CustomUserAdmin)
|
|
admin.site.register(Parcel, ParcelAdmin)
|
|
admin.site.register(Country, CountryAdmin)
|
|
admin.site.register(Governate)
|
|
admin.site.register(City)
|
|
admin.site.register(PlatformProfile, PlatformProfileAdmin)
|
|
admin.site.register(Testimonial, TestimonialAdmin)
|
|
admin.site.register(DriverRating)
|
|
admin.site.register(PricingRule, PricingRuleAdmin)
|
|
class NotificationTemplateAdmin(admin.ModelAdmin):
|
|
list_display = ('key', 'description')
|
|
readonly_fields = ('key', 'description', 'available_variables')
|
|
search_fields = ('key', 'description')
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('key', 'description', 'available_variables')
|
|
}),
|
|
(_('Email Content'), {
|
|
'fields': ('subject_en', 'subject_ar', 'email_body_en', 'email_body_ar'),
|
|
'description': _('For emails, the body is wrapped in a base template. Use HTML if needed.')
|
|
}),
|
|
(_('WhatsApp Content'), {
|
|
'fields': ('whatsapp_body_en', 'whatsapp_body_ar'),
|
|
'description': _('For WhatsApp, use plain text with newlines.')
|
|
}),
|
|
)
|
|
|
|
def has_add_permission(self, request):
|
|
return False # Prevent adding new keys manually
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
return False
|
|
|
|
admin.site.register(NotificationTemplate, NotificationTemplateAdmin) |