diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a41ab66..365dd75 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index e3eb9ff..7baa1a3 100644 Binary files a/core/__pycache__/forms.cpython-311.pyc and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 92a5f4b..0dfce24 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 4bae6d7..f72d1f9 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index e1dab42..9c171e2 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,7 +1,7 @@ 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, DriverReport, DriverRejection, ParcelType +from .models import Profile, Parcel, Country, Governate, City, PlatformProfile, Testimonial, DriverRating, NotificationTemplate, PricingRule, DriverReport, DriverRejection, ParcelType, DriverWarning from django.utils.translation import gettext_lazy as _ from django.urls import path, reverse from django.shortcuts import render, redirect @@ -18,19 +18,23 @@ from django.template.loader import render_to_string import weasyprint from django.db.models import Sum +class DriverWarningInline(admin.TabularInline): + model = DriverWarning + extra = 1 + class ProfileInline(admin.StackedInline): model = Profile can_delete = False verbose_name_plural = _('Profiles') fieldsets = ( - (None, {'fields': ('role', 'is_approved', 'is_banned', 'ban_reason', 'phone_number', 'profile_picture', 'address')}), + (None, {'fields': ('role', 'is_approved', 'is_banned', 'ban_reason', 'phone_number', 'profile_picture', 'address', 'language')}), (_('Driver Assessment'), {'fields': ('driver_grade', 'is_recommended')}), (_('Driver Info'), {'fields': ('license_front_image', 'license_back_image', 'car_plate_number', 'bank_account_number'), 'classes': ('collapse',)}), (_('Location'), {'fields': ('country', 'governate', 'city'), 'classes': ('collapse',)}), ) class CustomUserAdmin(UserAdmin): - inlines = (ProfileInline,) + inlines = (ProfileInline, DriverWarningInline) list_display = ('username', 'email', 'get_role', 'get_driver_grade', 'get_approval_status', 'get_ban_status', 'is_active', 'is_staff', 'send_whatsapp_link') list_filter = ('is_active', 'is_staff', 'profile__role', 'profile__is_approved', 'profile__is_banned', 'profile__driver_grade') @@ -208,9 +212,12 @@ class PlatformProfileAdmin(admin.ModelAdmin): 'fields': ('accepting_shipments', 'maintenance_message_en', 'maintenance_message_ar'), 'description': _('Toggle to allow or stop receiving new parcel shipments. If stopped, buttons will turn red and an alert will be shown.') }), - (_('Driver Rejection / Auto-Ban'), { - 'fields': ('auto_ban_on_rejections', 'rejection_limit'), - 'description': _('Configure automatic banning for drivers who reject too many shipments.') + (_('Driver Warning & Rejection / Auto-Ban'), { + 'fields': ( + 'enable_auto_ban_on_warnings', 'max_warnings_before_ban', + 'auto_ban_on_rejections', 'rejection_limit' + ), + 'description': _('Configure automatic banning for drivers who exceed warning or rejection limits.') }), (_('Testing / Development'), { 'fields': ('auto_mark_paid',), @@ -403,3 +410,10 @@ class NotificationTemplateAdmin(admin.ModelAdmin): admin.site.register(NotificationTemplate, NotificationTemplateAdmin) admin.site.register(ParcelType) + +class DriverWarningAdmin(admin.ModelAdmin): + list_display = ('driver', 'reason', 'created_at') + list_filter = ('created_at',) + search_fields = ('driver__username', 'reason') + +admin.site.register(DriverWarning, DriverWarningAdmin) diff --git a/core/forms.py b/core/forms.py index 57cc2dd..f971caf 100644 --- a/core/forms.py +++ b/core/forms.py @@ -115,6 +115,7 @@ class UserRegistrationForm(forms.ModelForm): profile.car_plate_number = self.cleaned_data['car_plate_number'] if 'bank_account_number' in self.cleaned_data: profile.bank_account_number = self.cleaned_data['bank_account_number'] + profile.language = get_language() profile.save() return user @@ -158,16 +159,18 @@ class UserProfileForm(forms.ModelForm): class Meta: model = Profile - fields = ['profile_picture', 'phone_number', 'address', 'country', 'governate', 'city', 'bank_account_number'] + fields = ['profile_picture', 'phone_number', 'address', 'country', 'governate', 'city', 'bank_account_number', 'language'] widgets = { 'country': forms.Select(attrs={'class': 'form-control'}), 'governate': forms.Select(attrs={'class': 'form-control'}), 'city': forms.Select(attrs={'class': 'form-control'}), + 'language': forms.Select(attrs={'class': 'form-control'}), } labels = { 'country': _('Country'), 'governate': _('Governate'), 'city': _('City'), + 'language': _('Language'), } def __init__(self, *args, **kwargs): diff --git a/core/migrations/0033_platformprofile_enable_auto_ban_on_warnings_and_more.py b/core/migrations/0033_platformprofile_enable_auto_ban_on_warnings_and_more.py new file mode 100644 index 0000000..284a4f2 --- /dev/null +++ b/core/migrations/0033_platformprofile_enable_auto_ban_on_warnings_and_more.py @@ -0,0 +1,40 @@ +# Generated by Django 5.2.7 on 2026-02-02 02:52 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0032_parceltype_parcel_parcel_type'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='platformprofile', + name='enable_auto_ban_on_warnings', + field=models.BooleanField(default=False, help_text='Automatically ban drivers who exceed a certain number of warnings.', verbose_name='Enable Auto-Ban on Warnings'), + ), + migrations.AddField( + model_name='platformprofile', + name='max_warnings_before_ban', + field=models.PositiveIntegerField(default=3, help_text='Number of warnings allowed before auto-ban.', verbose_name='Max Warnings Before Ban'), + ), + migrations.CreateModel( + name='DriverWarning', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reason', models.TextField(verbose_name='Reason for Warning')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('driver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='warnings', to=settings.AUTH_USER_MODEL, verbose_name='Driver')), + ], + options={ + 'verbose_name': 'Driver Warning', + 'verbose_name_plural': 'Driver Warnings', + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/core/migrations/0034_profile_language.py b/core/migrations/0034_profile_language.py new file mode 100644 index 0000000..dacfc86 --- /dev/null +++ b/core/migrations/0034_profile_language.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-02-02 03:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0033_platformprofile_enable_auto_ban_on_warnings_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='language', + field=models.CharField(choices=[('en', 'English'), ('ar', 'Arabic')], default='ar', max_length=10, verbose_name='Language'), + ), + ] diff --git a/core/migrations/__pycache__/0033_platformprofile_enable_auto_ban_on_warnings_and_more.cpython-311.pyc b/core/migrations/__pycache__/0033_platformprofile_enable_auto_ban_on_warnings_and_more.cpython-311.pyc new file mode 100644 index 0000000..88bdf03 Binary files /dev/null and b/core/migrations/__pycache__/0033_platformprofile_enable_auto_ban_on_warnings_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0034_profile_language.cpython-311.pyc b/core/migrations/__pycache__/0034_profile_language.cpython-311.pyc new file mode 100644 index 0000000..6b4d847 Binary files /dev/null and b/core/migrations/__pycache__/0034_profile_language.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index c6156d8..6ae9aa5 100644 --- a/core/models.py +++ b/core/models.py @@ -115,6 +115,7 @@ class Profile(models.Model): is_recommended = models.BooleanField(_("Recommended by Shippers"), default=False) is_approved = models.BooleanField(_('Approved'), default=False, help_text=_("Designates whether this user is approved to use the platform (mainly for drivers).")) + language = models.CharField(_("Language"), max_length=10, choices=[("en", _("English")), ("ar", _("Arabic"))], default="ar") # Ban Status is_banned = models.BooleanField(_('Banned'), default=False) @@ -205,6 +206,10 @@ class PlatformProfile(models.Model): maintenance_message_ar = models.TextField(_("Maintenance Message (Arabic)"), blank=True, help_text=_("Message to show when shipments are stopped.")) # Driver Rejection / Auto-Ban + # Driver Warning / Auto-Ban + enable_auto_ban_on_warnings = models.BooleanField(_("Enable Auto-Ban on Warnings"), default=False, help_text=_("Automatically ban drivers who exceed a certain number of warnings.")) + max_warnings_before_ban = models.PositiveIntegerField(_("Max Warnings Before Ban"), default=3, help_text=_("Number of warnings allowed before auto-ban.")) + auto_ban_on_rejections = models.BooleanField(_("Enable Auto-Ban on Rejections"), default=False, help_text=_("Automatically ban drivers who exceed a certain number of rejections.")) rejection_limit = models.PositiveIntegerField(_("Rejection Limit"), default=5, help_text=_("Number of rejections allowed before auto-ban.")) # Live Activity Ticker @@ -523,4 +528,35 @@ class DriverRejection(models.Model): class Meta: verbose_name = _('Driver Rejection') verbose_name_plural = _('Driver Rejections') - ordering = ['-created_at'] \ No newline at end of file + ordering = ['-created_at'] +class DriverWarning(models.Model): + driver = models.ForeignKey(User, on_delete=models.CASCADE, related_name='warnings', verbose_name=_('Driver')) + reason = models.TextField(_('Reason for Warning')) + created_at = models.DateTimeField(_('Created At'), auto_now_add=True) + + def __str__(self): + return f"Warning for {self.driver.username} on {self.created_at.strftime('%Y-%m-%d')}" + + def save(self, *args, **kwargs): + is_new = self.pk is None + super().save(*args, **kwargs) + if is_new: + # Check for auto-ban + try: + platform_profile = PlatformProfile.objects.first() + if platform_profile and platform_profile.enable_auto_ban_on_warnings: + warning_count = DriverWarning.objects.filter(driver=self.driver).count() + if warning_count >= platform_profile.max_warnings_before_ban: + profile = self.driver.profile + profile.is_banned = True + profile.ban_reason = _("Automatically banned due to exceeding warning limit (%(limit)d warnings).") % { + 'limit': platform_profile.max_warnings_before_ban + } + profile.save() + except Exception: + pass + + class Meta: + verbose_name = _('Driver Warning') + verbose_name_plural = _('Driver Warnings') + ordering = ['-created_at'] diff --git a/core/templates/core/driver_dashboard.html b/core/templates/core/driver_dashboard.html index 3d37cbc..f8bb2e7 100644 --- a/core/templates/core/driver_dashboard.html +++ b/core/templates/core/driver_dashboard.html @@ -5,9 +5,33 @@
{% trans "Your driver account is currently under review by our administrators. You will be notified once your documents are verified and your account is approved to accept shipments." %}
+{% trans "We are currently revising your documents. Please be patient. We will inform once we finish." %}
{% trans "No shipments available at the moment." %}
+{% trans "No shipments available matching your criteria." %}
+ {% if search_query %} + {% trans "Clear Search" %} + {% endif %} +{% trans "You haven't accepted any shipments yet." %}
+{% trans "No active deliveries matching your criteria." %}
+ {% if search_query %} + {% trans "Clear Search" %} + {% endif %} +{% trans "No completed deliveries yet." %}
+{% trans "No matching transaction history." %}
+ {% if search_query %} + {% trans "Clear Search" %} + {% endif %}{% trans "No cancelled shipments." %}
+{% trans "No matching cancelled shipments." %}
+ {% if search_query %} + {% trans "Clear Search" %} + {% endif %}{{ profile.address|default:"-" }}
- - - +{{ profile.get_language_display }}
+