diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc
index 93e6085..1b97a82 100644
Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ
diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc
index 2e8ff7d..4bdc403 100644
Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ
diff --git a/core/admin.py b/core/admin.py
index 61d387a..097ec01 100644
--- a/core/admin.py
+++ b/core/admin.py
@@ -6,6 +6,7 @@ from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.utils.html import format_html
+import os
from .models import Profile, Truck, Shipment, Bid, Message, WhatsAppConfig, Country, City, TruckType, AppSetting, Banner, HomeSection
from .whatsapp import send_whatsapp_message
@@ -38,7 +39,14 @@ class ProfileAdmin(admin.ModelAdmin):
if obj.is_expired():
return format_html('{}', _('Expired'))
- days = obj.days_until_expiry()
+ # Profile model might not have days_until_expiry, let's check
+ # Based on models.py read earlier, it doesn't have it.
+ # It has a commented out section that seems to be a start of it.
+ # I'll use inline calculation
+ if not obj.subscription_expiry:
+ return format_html('{}', _('No Expiry'))
+
+ days = (obj.subscription_expiry - timezone.now().date()).days
if days <= 7:
return format_html('{} ({} {})', _('Expiring soon'), days, _('days'))
@@ -48,9 +56,56 @@ class ProfileAdmin(admin.ModelAdmin):
@admin.register(Truck)
class TruckAdmin(admin.ModelAdmin):
- list_display = ('display_truck_type', 'model', 'plate_no', 'owner', 'load_capacity')
+ list_display = ('display_truck_type', 'model', 'plate_no', 'owner', 'load_capacity', 'is_approved')
search_fields = ('plate_no', 'owner__username', 'model')
list_filter = ('truck_type_link', 'is_approved')
+ readonly_fields = ('truck_picture_preview', 'registration_front_preview', 'registration_back_preview', 'driver_license_front_preview', 'driver_license_back_preview')
+
+ def truck_picture_preview(self, obj):
+ if obj.truck_picture:
+ return format_html('', obj.truck_picture.url)
+ return "-"
+ truck_picture_preview.short_description = _("Truck Picture Preview")
+
+ def _file_preview(self, field):
+ if not field:
+ return "-"
+ url = field.url
+ ext = os.path.splitext(field.name)[1].lower()
+ if ext in ['.jpg', '.jpeg', '.png', '.webp', '.gif']:
+ return format_html('
', url, url)
+ elif ext == '.pdf':
+ return format_html('
{}', url, _("View PDF Document"))
+ return format_html('{}', url, _("View File"))
+
+ def registration_front_preview(self, obj):
+ return self._file_preview(obj.registration_front)
+ registration_front_preview.short_description = _("Registration Front Preview")
+
+ def registration_back_preview(self, obj):
+ return self._file_preview(obj.registration_back)
+ registration_back_preview.short_description = _("Registration Back Preview")
+
+ def driver_license_front_preview(self, obj):
+ return self._file_preview(obj.driver_license_front)
+ driver_license_front_preview.short_description = _("Driver License Front Preview")
+
+ def driver_license_back_preview(self, obj):
+ return self._file_preview(obj.driver_license_back)
+ driver_license_back_preview.short_description = _("Driver License Back Preview")
+
+ fieldsets = (
+ (None, {'fields': ('owner', 'truck_type_link', 'is_approved')}),
+ (_('Details (EN)'), {'fields': ('truck_type', 'model', 'load_capacity', 'color', 'year', 'plate_no', 'registration_expiry_date')}),
+ (_('Details (AR)'), {'fields': ('truck_type_ar', 'model_ar', 'load_capacity_ar', 'color_ar')}),
+ (_('Pictures & Documents'), {'fields': (
+ 'truck_picture', 'truck_picture_preview',
+ 'registration_front', 'registration_front_preview',
+ 'registration_back', 'registration_back_preview',
+ 'driver_license_front', 'driver_license_front_preview',
+ 'driver_license_back', 'driver_license_back_preview'
+ )}),
+ )
@admin.register(Shipment)
class ShipmentAdmin(admin.ModelAdmin):
@@ -134,4 +189,4 @@ class HomeSectionAdmin(admin.ModelAdmin):
list_display = ('title', 'section_type', 'order', 'is_active')
list_editable = ('order', 'is_active')
list_filter = ('section_type', 'is_active', 'background_color')
- search_fields = ('title', 'title_ar', 'subtitle', 'subtitle_ar', 'content', 'content_ar')
+ search_fields = ('title', 'title_ar', 'subtitle', 'subtitle_ar', 'content', 'content_ar')
\ No newline at end of file
diff --git a/core/migrations/0022_alter_truck_driver_license_back_and_more.py b/core/migrations/0022_alter_truck_driver_license_back_and_more.py
new file mode 100644
index 0000000..a8680e1
--- /dev/null
+++ b/core/migrations/0022_alter_truck_driver_license_back_and_more.py
@@ -0,0 +1,33 @@
+# Generated by Django 5.2.7 on 2026-01-24 07:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0021_appsetting_thawani_enabled'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='truck',
+ name='driver_license_back',
+ field=models.FileField(blank=True, null=True, upload_to='docs/', verbose_name='Driver License (Back Face)'),
+ ),
+ migrations.AlterField(
+ model_name='truck',
+ name='driver_license_front',
+ field=models.FileField(blank=True, null=True, upload_to='docs/', verbose_name='Driver License (Front Face)'),
+ ),
+ migrations.AlterField(
+ model_name='truck',
+ name='registration_back',
+ field=models.FileField(blank=True, null=True, upload_to='docs/', verbose_name='Registration (Back Face)'),
+ ),
+ migrations.AlterField(
+ model_name='truck',
+ name='registration_front',
+ field=models.FileField(blank=True, null=True, upload_to='docs/', verbose_name='Registration (Front Face)'),
+ ),
+ ]
diff --git a/core/migrations/__pycache__/0022_alter_truck_driver_license_back_and_more.cpython-311.pyc b/core/migrations/__pycache__/0022_alter_truck_driver_license_back_and_more.cpython-311.pyc
new file mode 100644
index 0000000..662d902
Binary files /dev/null and b/core/migrations/__pycache__/0022_alter_truck_driver_license_back_and_more.cpython-311.pyc differ
diff --git a/core/models.py b/core/models.py
index 3384816..d8c35ff 100644
--- a/core/models.py
+++ b/core/models.py
@@ -5,8 +5,12 @@ from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.utils.translation import get_language
from django.utils import timezone
+from django.core.files import File
import random
import string
+import os
+from io import BytesIO
+from PIL import Image
class Country(models.Model):
name = models.CharField(_('Country Name'), max_length=100)
@@ -127,10 +131,10 @@ class Truck(models.Model):
# Pictures
truck_picture = models.ImageField(_('Truck Picture'), upload_to='trucks/', blank=True, null=True)
- registration_front = models.ImageField(_('Registration (Front Face)'), upload_to='docs/', blank=True, null=True)
- registration_back = models.ImageField(_('Registration (Back Face)'), upload_to='docs/', blank=True, null=True)
- driver_license_front = models.ImageField(_('Driver License (Front Face)'), upload_to='docs/', blank=True, null=True)
- driver_license_back = models.ImageField(_('Driver License (Back Face)'), upload_to='docs/', blank=True, null=True)
+ registration_front = models.FileField(_('Registration (Front Face)'), upload_to='docs/', blank=True, null=True)
+ registration_back = models.FileField(_('Registration (Back Face)'), upload_to='docs/', blank=True, null=True)
+ driver_license_front = models.FileField(_('Driver License (Front Face)'), upload_to='docs/', blank=True, null=True)
+ driver_license_back = models.FileField(_('Driver License (Back Face)'), upload_to='docs/', blank=True, null=True)
is_approved = models.BooleanField(_('Is Approved'), default=False)
created_at = models.DateTimeField(auto_now_add=True)
@@ -166,6 +170,55 @@ class Truck(models.Model):
return self.color_ar
return self.color
+ def save(self, *args, **kwargs):
+ # Compress images
+ if self.truck_picture:
+ self.truck_picture = self.compress_image(self.truck_picture)
+
+ # For docs, compress only if they are images
+ if self.registration_front:
+ self.registration_front = self.compress_image(self.registration_front)
+ if self.registration_back:
+ self.registration_back = self.compress_image(self.registration_back)
+ if self.driver_license_front:
+ self.driver_license_front = self.compress_image(self.driver_license_front)
+ if self.driver_license_back:
+ self.driver_license_back = self.compress_image(self.driver_license_back)
+
+ super().save(*args, **kwargs)
+
+ def compress_image(self, image_field):
+ if not image_field:
+ return image_field
+
+ try:
+ # Check file extension
+ ext = os.path.splitext(image_field.name)[1].lower()
+ if ext not in ['.jpg', '.jpeg', '.png', '.webp']:
+ return image_field
+
+ img = Image.open(image_field)
+
+ # If already small enough, don't compress (optional, but good for performance)
+ # if image_field.size < 300 * 1024: return image_field
+
+ if img.mode != 'RGB':
+ img = img.convert('RGB')
+
+ # Resize if too large
+ max_size = (1200, 1200)
+ img.thumbnail(max_size, Image.LANCZOS)
+
+ output = BytesIO()
+ img.save(output, format='JPEG', quality=70, optimize=True)
+ output.seek(0)
+
+ new_name = os.path.splitext(image_field.name)[0] + '.jpg'
+ return File(output, name=new_name)
+ except Exception as e:
+ # Not an image or other error, return as is
+ return image_field
+
class Shipment(models.Model):
STATUS_CHOICES = (
('OPEN', _('Open for Bids')),
@@ -421,4 +474,4 @@ class Transaction(models.Model):
super().save(*args, **kwargs)
def __str__(self):
- return f"{self.receipt_number} - {self.user.username} ({self.amount})"
+ return f"{self.receipt_number} - {self.user.username} ({self.amount})"
\ No newline at end of file
diff --git a/core/templates/core/truck_register.html b/core/templates/core/truck_register.html
index 5530196..3e8f942 100644
--- a/core/templates/core/truck_register.html
+++ b/core/templates/core/truck_register.html
@@ -125,6 +125,7 @@
{% trans "Photos will be automatically resized for better performance. PDF files are accepted for documents." %}