570 lines
23 KiB
Python
570 lines
23 KiB
Python
from django.db import models
|
|
from django.contrib.auth.models import User, Group
|
|
from django.db.models.signals import post_save
|
|
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)
|
|
code = models.CharField(_('Country Code'), max_length=10)
|
|
is_default = models.BooleanField(_('Is Default'), default=False)
|
|
|
|
class Meta:
|
|
verbose_name = _('Country')
|
|
verbose_name_plural = _('Countries')
|
|
|
|
def __str__(self):
|
|
return f"{self.name} (+{self.code})"
|
|
|
|
class City(models.Model):
|
|
country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='cities')
|
|
name = models.CharField(_('City Name'), max_length=100)
|
|
|
|
class Meta:
|
|
verbose_name = _('City')
|
|
verbose_name_plural = _('Cities')
|
|
ordering = ['name']
|
|
|
|
def __str__(self):
|
|
return f"{self.name} ({self.country.name})"
|
|
|
|
class TruckType(models.Model):
|
|
name = models.CharField(_('Name (EN)'), max_length=100)
|
|
name_ar = models.CharField(_('Name (AR)'), max_length=100, blank=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('Truck Type')
|
|
verbose_name_plural = _('Truck Types')
|
|
|
|
def __str__(self):
|
|
if get_language() == 'ar' and self.name_ar:
|
|
return self.name_ar
|
|
return self.name
|
|
|
|
class Profile(models.Model):
|
|
ROLE_CHOICES = (
|
|
('SHIPPER', _('Shipper (Need Goods Moved)')),
|
|
('TRUCK_OWNER', _('Truck Owner (Service Provider)')),
|
|
('ADMIN', _('Administrator')),
|
|
)
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
|
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='SHIPPER')
|
|
SUBSCRIPTION_CHOICES = (
|
|
('MONTHLY', _('Monthly')),
|
|
('ANNUAL', _('Annual')),
|
|
('NONE', _('None')),
|
|
)
|
|
subscription_plan = models.CharField(max_length=20, choices=SUBSCRIPTION_CHOICES, default='NONE')
|
|
subscription_expiry = models.DateField(null=True, blank=True)
|
|
is_subscription_active = models.BooleanField(default=False)
|
|
|
|
# New Profile Picture field
|
|
profile_picture = models.ImageField(_('Profile Picture'), upload_to='profiles/', blank=True, null=True)
|
|
|
|
def is_expired(self):
|
|
if self.subscription_plan == "NONE":
|
|
return False
|
|
if not self.is_subscription_active:
|
|
return True
|
|
if not self.subscription_expiry:
|
|
return True
|
|
return self.subscription_expiry < timezone.now().date()
|
|
|
|
country_code = models.CharField(max_length=5, blank=True, default="966")
|
|
phone_number = models.CharField(max_length=20, unique=True, null=True) # Changed to unique and nullable for migration safety
|
|
|
|
@property
|
|
def full_phone_number(self):
|
|
if not self.phone_number:
|
|
return ""
|
|
# Remove any existing leading + from country code and phone number
|
|
cc = str(self.country_code).replace("+", "").strip()
|
|
pn = str(self.phone_number).replace("+", "").strip()
|
|
return f"{cc}{pn}"
|
|
|
|
def __str__(self):
|
|
return f"{self.user.username} - {self.role}"
|
|
|
|
def save(self, *args, **kwargs):
|
|
if self.profile_picture:
|
|
self.profile_picture = self.compress_image(self.profile_picture)
|
|
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 img.mode != 'RGB':
|
|
img = img.convert('RGB')
|
|
|
|
# Resize if too large
|
|
max_size = (500, 500)
|
|
img.thumbnail(max_size, Image.LANCZOS)
|
|
|
|
output = BytesIO()
|
|
img.save(output, format='JPEG', quality=80, 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 OTPCode(models.Model):
|
|
phone_number = models.CharField(max_length=20, null=True, blank=True)
|
|
email = models.EmailField(null=True, blank=True)
|
|
code = models.CharField(max_length=6)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
is_used = models.BooleanField(default=False)
|
|
|
|
def is_valid(self):
|
|
# Valid for 10 minutes
|
|
return not self.is_used and (timezone.now() - self.created_at).total_seconds() < 600
|
|
|
|
@staticmethod
|
|
def generate_code(phone_number=None, email=None):
|
|
code = ''.join(random.choices(string.digits, k=6))
|
|
return OTPCode.objects.create(phone_number=phone_number, email=email, code=code)
|
|
|
|
class Truck(models.Model):
|
|
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='trucks')
|
|
|
|
# Using a fresh name to avoid conflict with partially created fields
|
|
truck_type_link = models.ForeignKey(TruckType, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Truck Type (New)'))
|
|
|
|
# English fields
|
|
truck_type = models.CharField(_('Truck Type (EN)'), max_length=100, blank=True)
|
|
model = models.CharField(_('Model (EN)'), max_length=100)
|
|
load_capacity = models.CharField(_('Load Capacity (EN)'), max_length=100)
|
|
color = models.CharField(_('Color (EN)'), max_length=50)
|
|
|
|
# Arabic fields
|
|
truck_type_ar = models.CharField(_('Truck Type (AR)'), max_length=100, blank=True)
|
|
model_ar = models.CharField(_('Model (AR)'), max_length=100, blank=True)
|
|
load_capacity_ar = models.CharField(_('Load Capacity (AR)'), max_length=100, blank=True)
|
|
color_ar = models.CharField(_('Color (AR)'), max_length=50, blank=True)
|
|
|
|
year = models.PositiveIntegerField(_('Year'))
|
|
plate_no = models.CharField(_('Plate No'), max_length=50)
|
|
registration_expiry_date = models.DateField(_('Registration Expiry Date'), null=True, blank=True)
|
|
|
|
# Pictures
|
|
truck_picture = models.ImageField(_('Truck Picture'), upload_to='trucks/', 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)
|
|
|
|
def __str__(self):
|
|
if self.truck_type_link:
|
|
return f"{self.truck_type_link} - {self.plate_no}"
|
|
return f"{self.truck_type} - {self.plate_no}"
|
|
|
|
@property
|
|
def display_truck_type(self):
|
|
if self.truck_type_link:
|
|
return str(self.truck_type_link)
|
|
if get_language() == 'ar' and self.truck_type_ar:
|
|
return self.truck_type_ar
|
|
return self.truck_type
|
|
|
|
@property
|
|
def display_model(self):
|
|
if get_language() == 'ar' and self.model_ar:
|
|
return self.model_ar
|
|
return self.model
|
|
|
|
@property
|
|
def display_load_capacity(self):
|
|
if get_language() == 'ar' and self.load_capacity_ar:
|
|
return self.load_capacity_ar
|
|
return self.load_capacity
|
|
|
|
@property
|
|
def display_color(self):
|
|
if get_language() == 'ar' and self.color_ar:
|
|
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')),
|
|
('IN_PROGRESS', _('In Progress')),
|
|
('COMPLETED', _('Completed')),
|
|
('CANCELLED', _('Cancelled')),
|
|
)
|
|
shipper = models.ForeignKey(User, on_delete=models.CASCADE, related_name='shipments')
|
|
description = models.TextField(_('Goods Description'))
|
|
weight = models.CharField(_('Weight/Volume'), max_length=100)
|
|
|
|
# Using a fresh name
|
|
required_truck_type_link = models.ForeignKey(TruckType, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Required Truck Type'))
|
|
|
|
origin_country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True, related_name='shipments_origin')
|
|
origin_city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, related_name='shipments_origin')
|
|
destination_country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True, related_name='shipments_destination')
|
|
destination_city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, related_name='shipments_destination')
|
|
|
|
origin = models.CharField(_('Origin (Legacy)'), max_length=255, blank=True)
|
|
destination = models.CharField(_('Destination (Legacy)'), max_length=255, blank=True)
|
|
|
|
delivery_date = models.DateField(_('Requested Delivery Date'))
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='OPEN')
|
|
|
|
assigned_truck = models.ForeignKey(Truck, on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_shipments')
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return f"{self.display_origin} to {self.display_destination} - {self.status}"
|
|
|
|
@property
|
|
def display_origin(self):
|
|
if self.origin_city and self.origin_country:
|
|
return f"{self.origin_city.name}, {self.origin_country.name}"
|
|
return self.origin
|
|
|
|
@property
|
|
def display_destination(self):
|
|
if self.destination_city and self.destination_country:
|
|
return f"{self.destination_city.name}, {self.destination_country.name}"
|
|
return self.destination
|
|
|
|
class Bid(models.Model):
|
|
STATUS_CHOICES = (
|
|
('PENDING', _('Pending')),
|
|
('ACCEPTED', _('Accepted')),
|
|
('REJECTED', _('Rejected')),
|
|
)
|
|
shipment = models.ForeignKey(Shipment, on_delete=models.CASCADE, related_name='bids')
|
|
truck_owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='bids')
|
|
truck = models.ForeignKey(Truck, on_delete=models.CASCADE)
|
|
amount = models.DecimalField(_('Offer Amount'), max_digits=10, decimal_places=2)
|
|
comments = models.TextField(_('Comments'), blank=True)
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING')
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return f"Bid by {self.truck_owner.username} for {self.shipment}"
|
|
|
|
class Message(models.Model):
|
|
shipment = models.ForeignKey(Shipment, on_delete=models.CASCADE, related_name='messages')
|
|
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_messages')
|
|
content = models.TextField()
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return f"From {self.sender.username} at {self.timestamp}"
|
|
|
|
class WhatsAppConfig(models.Model):
|
|
api_token = models.CharField(_('Wablas API Token'), max_length=255)
|
|
secret_key = models.CharField(_('Wablas Secret Key'), max_length=255, blank=True, null=True)
|
|
is_active = models.BooleanField(_('Is Active'), default=True)
|
|
admin_phone = models.CharField(_("Admin Notification Phone"), max_length=20, blank=True, null=True, help_text=_("WhatsApp number to receive admin notifications (with country code, e.g., 96812345678)"))
|
|
|
|
class Meta:
|
|
verbose_name = _('WhatsApp Configuration')
|
|
verbose_name_plural = _('WhatsApp Configuration')
|
|
|
|
def __str__(self):
|
|
return str(_("WhatsApp Configuration"))
|
|
|
|
class AppSetting(models.Model):
|
|
app_name = models.CharField(_('App Name'), max_length=100)
|
|
logo = models.ImageField(_('Logo'), upload_to='app/', blank=True, null=True)
|
|
slogan = models.CharField(_('Slogan'), max_length=255, blank=True)
|
|
registration_number = models.CharField(_('Registration Number'), max_length=100, blank=True)
|
|
tax_number = models.CharField(_('Tax Number'), max_length=100, blank=True)
|
|
contact_phone = models.CharField(_('Contact Phone'), max_length=20, blank=True)
|
|
contact_email = models.EmailField(_('Contact Email'), blank=True)
|
|
contact_address = models.TextField(_('Contact Address'), blank=True)
|
|
terms_of_service = models.TextField(_('Terms of Service'), blank=True)
|
|
privacy_policy = models.TextField(_('Privacy Policy'), blank=True)
|
|
subscription_enabled = models.BooleanField(_('Enable Subscription Fee'), default=False)
|
|
thawani_enabled = models.BooleanField(_("Enable Thawani Payment"), default=True)
|
|
|
|
# Shipper Fees
|
|
shipper_monthly_fee = models.DecimalField(_('Shipper Monthly Fee'), max_digits=10, decimal_places=2, default=0.00)
|
|
shipper_annual_fee = models.DecimalField(_('Shipper Annual Fee'), max_digits=10, decimal_places=2, default=0.00)
|
|
|
|
# Truck Owner Fees
|
|
truck_owner_monthly_fee = models.DecimalField(_('Truck Owner Monthly Fee'), max_digits=10, decimal_places=2, default=0.00)
|
|
truck_owner_annual_fee = models.DecimalField(_('Truck Owner Annual Fee'), max_digits=10, decimal_places=2, default=0.00)
|
|
|
|
class Meta:
|
|
verbose_name = _('App Setting')
|
|
verbose_name_plural = _('App Settings')
|
|
|
|
def __str__(self):
|
|
return self.app_name
|
|
|
|
class Banner(models.Model):
|
|
title = models.CharField(_('Title (EN)'), max_length=200)
|
|
title_ar = models.CharField(_('Title (AR)'), max_length=200, blank=True)
|
|
subtitle = models.CharField(_('Subtitle (EN)'), max_length=255, blank=True)
|
|
subtitle_ar = models.CharField(_('Subtitle (AR)'), max_length=255, blank=True)
|
|
image = models.ImageField(_('Banner Image'), upload_to='banners/')
|
|
link = models.URLField(_('Link URL'), blank=True, null=True, help_text=_("Internal or external URL"))
|
|
is_active = models.BooleanField(_('Is Active'), default=True)
|
|
order = models.PositiveIntegerField(_('Order'), default=0)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('Banner')
|
|
verbose_name_plural = _('Banners')
|
|
ordering = ['order', '-created_at']
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
@property
|
|
def display_title(self):
|
|
if get_language() == 'ar' and self.title_ar:
|
|
return self.title_ar
|
|
return self.title
|
|
|
|
@property
|
|
def display_subtitle(self):
|
|
if get_language() == 'ar' and self.subtitle_ar:
|
|
return self.subtitle_ar
|
|
return self.subtitle
|
|
|
|
class HomeSection(models.Model):
|
|
SECTION_TYPES = (
|
|
('SIMPLE', _('Simple Text & Image')),
|
|
('FEATURES', _('Features List')),
|
|
('CTA', _('Call to Action')),
|
|
)
|
|
title = models.CharField(_('Title (EN)'), max_length=200)
|
|
title_ar = models.CharField(_('Title (AR)'), max_length=200, blank=True)
|
|
subtitle = models.CharField(_('Subtitle (EN)'), max_length=255, blank=True)
|
|
subtitle_ar = models.CharField(_('Subtitle (AR)'), max_length=255, blank=True)
|
|
content = models.TextField(_('Content (EN)'), blank=True)
|
|
content_ar = models.TextField(_('Content (AR)'), blank=True)
|
|
image = models.ImageField(_('Image'), upload_to='home_sections/', blank=True, null=True)
|
|
order = models.PositiveIntegerField(_('Order'), default=0)
|
|
is_active = models.BooleanField(_('Is Active'), default=True)
|
|
section_type = models.CharField(max_length=20, choices=SECTION_TYPES, default='SIMPLE')
|
|
background_color = models.CharField(max_length=50, default='white', help_text="e.g. white, light, primary")
|
|
|
|
class Meta:
|
|
verbose_name = _('Home Section')
|
|
verbose_name_plural = _('Home Sections')
|
|
ordering = ['order']
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
@property
|
|
def display_title(self):
|
|
if get_language() == 'ar' and self.title_ar:
|
|
return self.title_ar
|
|
return self.title
|
|
|
|
@property
|
|
def display_subtitle(self):
|
|
if get_language() == 'ar' and self.subtitle_ar:
|
|
return self.subtitle_ar
|
|
return self.subtitle
|
|
|
|
@property
|
|
def display_content(self):
|
|
if get_language() == 'ar' and self.content_ar:
|
|
return self.content_ar
|
|
return self.content
|
|
|
|
@receiver(post_save, sender=User)
|
|
def create_user_profile(sender, instance, created, **kwargs):
|
|
if created:
|
|
Profile.objects.create(user=instance)
|
|
|
|
@receiver(post_save, sender=User)
|
|
def save_user_profile(sender, instance, **kwargs):
|
|
if hasattr(instance, 'profile'):
|
|
instance.profile.save()
|
|
else:
|
|
Profile.objects.create(user=instance)
|
|
|
|
@receiver(post_save, sender=Profile)
|
|
def sync_user_groups(sender, instance, **kwargs):
|
|
"""
|
|
Automatically syncs Django Groups based on the Profile role.
|
|
"""
|
|
# Get or create the group for the current role
|
|
group, created = Group.objects.get_or_create(name=instance.role)
|
|
|
|
# Get all possible role-based groups to clean up
|
|
all_role_names = [role[0] for role in Profile.ROLE_CHOICES]
|
|
|
|
# Remove user from other role groups they might be in
|
|
other_groups = Group.objects.filter(name__in=all_role_names).exclude(name=instance.role)
|
|
instance.user.groups.remove(*other_groups)
|
|
|
|
# Add user to the correct group
|
|
instance.user.groups.add(group)
|
|
class Transaction(models.Model):
|
|
TRANSACTION_TYPES = (
|
|
('PAYMENT', _('Payment')),
|
|
('REFUND', _('Refund')),
|
|
)
|
|
STATUS_CHOICES = (
|
|
('PENDING', _('Pending')),
|
|
('COMPLETED', _('Completed')),
|
|
('FAILED', _('Failed')),
|
|
('CANCELLED', _('Cancelled')),
|
|
)
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='transactions')
|
|
amount = models.DecimalField(_('Amount'), max_digits=10, decimal_places=2)
|
|
transaction_type = models.CharField(max_length=20, choices=TRANSACTION_TYPES, default='PAYMENT')
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='COMPLETED')
|
|
description = models.TextField(_('Description'), blank=True)
|
|
payment_method = models.CharField(_('Payment Method'), max_length=100, blank=True)
|
|
reference_number = models.CharField(_('Reference Number'), max_length=100, blank=True)
|
|
receipt_number = models.CharField(_('Receipt Number'), max_length=20, unique=True, blank=True)
|
|
session_id = models.CharField(_("Session ID"), max_length=255, blank=True, null=True)
|
|
payment_status = models.CharField(_("Payment Status"), max_length=50, blank=True, null=True)
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('Transaction')
|
|
verbose_name_plural = _('Transactions')
|
|
ordering = ['-created_at']
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.receipt_number:
|
|
# Generate a unique receipt number: REC-YYYYMMDD-XXXX
|
|
date_str = timezone.now().strftime('%Y%m%d')
|
|
random_str = ''.join(random.choices(string.ascii_uppercase + string.digits, k=4))
|
|
self.receipt_number = f"REC-{date_str}-{random_str}"
|
|
super().save(*args, **kwargs)
|
|
|
|
def __str__(self):
|
|
return f"{self.receipt_number} - {self.user.username} ({self.amount})"
|
|
|
|
class ContactMessage(models.Model):
|
|
name = models.CharField(_('Name'), max_length=100)
|
|
email = models.EmailField(_('Email'))
|
|
subject = models.CharField(_('Subject'), max_length=200)
|
|
message = models.TextField(_('Message'))
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
is_read = models.BooleanField(default=False)
|
|
|
|
class Meta:
|
|
verbose_name = _('Contact Message')
|
|
verbose_name_plural = _('Contact Messages')
|
|
ordering = ['-created_at']
|
|
|
|
def __str__(self):
|
|
return f"{self.subject} - {self.name}"
|
|
|
|
class Testimonial(models.Model):
|
|
name = models.CharField(_('Customer Name (EN)'), max_length=100)
|
|
name_ar = models.CharField(_('Customer Name (AR)'), max_length=100, blank=True)
|
|
role = models.CharField(_('Customer Role (EN)'), max_length=100, blank=True)
|
|
role_ar = models.CharField(_('Customer Role (AR)'), max_length=100, blank=True)
|
|
content = models.TextField(_('Testimony (EN)'))
|
|
content_ar = models.TextField(_('Testimony (AR)'), blank=True)
|
|
rating = models.PositiveIntegerField(_('Rating (1-5)'), default=5)
|
|
image = models.ImageField(_('Customer Image'), upload_to='testimonials/', blank=True, null=True)
|
|
is_active = models.BooleanField(_('Is Active'), default=True)
|
|
order = models.PositiveIntegerField(_('Order'), default=0)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('Testimonial')
|
|
verbose_name_plural = _('Testimonials')
|
|
ordering = ['order', '-created_at']
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
@property
|
|
def display_name(self):
|
|
if get_language() == 'ar' and self.name_ar:
|
|
return self.name_ar
|
|
return self.name
|
|
|
|
@property
|
|
def display_role(self):
|
|
if get_language() == 'ar' and self.role_ar:
|
|
return self.role_ar
|
|
return self.role
|
|
|
|
@property
|
|
def display_content(self):
|
|
if get_language() == 'ar' and self.content_ar:
|
|
return self.content_ar
|
|
return self.content
|