add search to select fields, redirect logged in user to landingpage if they are authenticated

This commit is contained in:
gitea 2024-12-10 10:13:10 +00:00
parent 9eba42aa91
commit 2dfbb5fa9a
14 changed files with 506 additions and 323 deletions

20
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Django",
"type": "debugpy",
"request": "launch",
"args": [
"runserver",
"0.0.0.0:8888"
],
"django": true,
"autoStartBrowser": false,
"program": "${workspaceFolder}/manage.py"
}
]
}

View File

@ -26,7 +26,7 @@ SECRET_KEY = 'django-insecure-gc9bh4*3=b6hihdnaom0edjsbxh$5t)aap@e8p&340r7)*)qb8
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = ['10.10.1.109', 'localhost', '127.0.0.1', '192.168.1.135', '172.20.10.4'] ALLOWED_HOSTS = ['10.10.1.109','10.10.1.120', 'localhost', '127.0.0.1', '192.168.1.135', '172.20.10.4']
# Application definition # Application definition
@ -110,9 +110,9 @@ WSGI_APPLICATION = 'car_inventory.wsgi.application'
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django_prometheus.db.backends.postgresql", "ENGINE": "django_prometheus.db.backends.postgresql",
"NAME": "secondhaikal", "NAME": "haikal",
"USER": "f95166", "USER": "haikal",
"PASSWORD": "Kfsh&rc9788", "PASSWORD": "haikal",
"HOST": "localhost", "HOST": "localhost",
"PORT": 5432, "PORT": 5432,
} }

View File

@ -12,7 +12,6 @@ admin.site.register(models.CarRegistration)
admin.site.register(models.CustomCard) admin.site.register(models.CustomCard)
admin.site.register(models.CarSpecificationValue) admin.site.register(models.CarSpecificationValue)
@admin.register(models.CarMake) @admin.register(models.CarMake)
class CarMakeAdmin(admin.ModelAdmin): class CarMakeAdmin(admin.ModelAdmin):
list_display = ('name', 'arabic_name', 'is_sa_import') list_display = ('name', 'arabic_name', 'is_sa_import')
@ -51,18 +50,18 @@ class CarSeriesAdmin(admin.ModelAdmin):
verbose_name = "Car Series" verbose_name = "Car Series"
@admin.register(models.CarTrim) # @admin.register(models.CarTrim)
class CarTrimAdmin(admin.ModelAdmin): # class CarTrimAdmin(admin.ModelAdmin):
list_display = ('name', # list_display = ('name',
'id_car_serie__name', # 'id_car_serie__name',
'id_car_serie__id_car_model__name', # 'id_car_serie__id_car_model__name',
'id_car_serie__id_car_model__id_car_make__name') # 'id_car_serie__id_car_model__id_car_make__name')
search_fields = ('name', 'arabic_name', 'id_car_serie__id_car_model__name') # search_fields = ('name', 'arabic_name', 'id_car_serie__id_car_model__name')
list_filter = ('id_car_serie__id_car_model__id_car_make__is_sa_import', # list_filter = ('id_car_serie__id_car_model__id_car_make__is_sa_import',
'id_car_serie__id_car_model__id_car_make__name') # 'id_car_serie__id_car_model__id_car_make__name')
class Meta: # class Meta:
verbose_name = "Car Trim" # verbose_name = "Car Trim"
@admin.register(models.CarSpecification) @admin.register(models.CarSpecification)

View File

@ -0,0 +1,37 @@
from django.core.management.base import BaseCommand
from faker import Faker
from inventory.models import Customer, Dealer
class Command(BaseCommand):
help = 'Seed the Customer model with 20 records'
def handle(self, *args, **kwargs):
fake = Faker()
dealers = Dealer.objects.all()
if not dealers.exists():
self.stdout.write(self.style.ERROR('No dealers found. Please create dealers first.'))
return
for _ in range(20):
dealer = fake.random_element(elements=dealers)
first_name = fake.first_name()
middle_name = fake.first_name() if fake.boolean() else ''
last_name = fake.last_name()
email = fake.unique.email()
national_id = fake.unique.bothify(text='##########')
phone_number = fake.unique.phone_number()
address = fake.address()
Customer.objects.create(
dealer=dealer,
first_name=first_name,
middle_name=middle_name,
last_name=last_name,
email=email,
national_id=national_id,
phone_number=phone_number,
address=address
)
self.stdout.write(self.style.SUCCESS('Successfully seeded 20 customers.'))

View File

@ -14,7 +14,6 @@ from django_ledger.models import (
UnitOfMeasureModel, UnitOfMeasureModel,
CustomerModel, CustomerModel,
ItemModelQuerySet, ItemModelQuerySet,
) )
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -27,7 +26,7 @@ class CarMake(models.Model, LocalizedNameMixin):
id_car_make = models.AutoField(primary_key=True) id_car_make = models.AutoField(primary_key=True)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
arabic_name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255)
logo = models.ImageField(_('logo'), upload_to='car_make', blank=True, null=True) logo = models.ImageField(_("logo"), upload_to="car_make", blank=True, null=True)
is_sa_import = models.BooleanField(default=False) is_sa_import = models.BooleanField(default=False)
def __str__(self): def __str__(self):
@ -39,7 +38,7 @@ class CarMake(models.Model, LocalizedNameMixin):
class CarModel(models.Model, LocalizedNameMixin): class CarModel(models.Model, LocalizedNameMixin):
id_car_model = models.AutoField(primary_key=True) id_car_model = models.AutoField(primary_key=True)
id_car_make = models.ForeignKey(CarMake, models.DO_NOTHING, db_column='id_car_make') id_car_make = models.ForeignKey(CarMake, models.DO_NOTHING, db_column="id_car_make")
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
arabic_name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255)
@ -52,7 +51,9 @@ class CarModel(models.Model, LocalizedNameMixin):
class CarSerie(models.Model, LocalizedNameMixin): class CarSerie(models.Model, LocalizedNameMixin):
id_car_serie = models.AutoField(primary_key=True) id_car_serie = models.AutoField(primary_key=True)
id_car_model = models.ForeignKey(CarModel, models.DO_NOTHING, db_column='id_car_model') id_car_model = models.ForeignKey(
CarModel, models.DO_NOTHING, db_column="id_car_model"
)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
arabic_name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255)
@ -65,7 +66,9 @@ class CarSerie(models.Model, LocalizedNameMixin):
class CarTrim(models.Model, LocalizedNameMixin): class CarTrim(models.Model, LocalizedNameMixin):
id_car_trim = models.AutoField(primary_key=True) id_car_trim = models.AutoField(primary_key=True)
id_car_serie = models.ForeignKey(CarSerie, models.DO_NOTHING, db_column='id_car_serie') id_car_serie = models.ForeignKey(
CarSerie, models.DO_NOTHING, db_column="id_car_serie"
)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
arabic_name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255)
start_production_year = models.IntegerField(blank=True, null=True) start_production_year = models.IntegerField(blank=True, null=True)
@ -82,7 +85,9 @@ class CarSpecification(models.Model, LocalizedNameMixin):
id_car_specification = models.AutoField(primary_key=True) id_car_specification = models.AutoField(primary_key=True)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
arabic_name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255)
id_parent = models.ForeignKey('self', models.DO_NOTHING, db_column='id_parent', blank=True, null=True) id_parent = models.ForeignKey(
"self", models.DO_NOTHING, db_column="id_parent", blank=True, null=True
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -93,8 +98,10 @@ class CarSpecification(models.Model, LocalizedNameMixin):
class CarSpecificationValue(models.Model): class CarSpecificationValue(models.Model):
id_car_specification_value = models.AutoField(primary_key=True) id_car_specification_value = models.AutoField(primary_key=True)
id_car_trim = models.ForeignKey(CarTrim, models.DO_NOTHING, db_column='id_car_trim') id_car_trim = models.ForeignKey(CarTrim, models.DO_NOTHING, db_column="id_car_trim")
id_car_specification = models.ForeignKey(CarSpecification, models.DO_NOTHING, db_column='id_car_specification') id_car_specification = models.ForeignKey(
CarSpecification, models.DO_NOTHING, db_column="id_car_specification"
)
value = models.CharField(max_length=500) value = models.CharField(max_length=500)
unit = models.CharField(max_length=255, blank=True, null=True) unit = models.CharField(max_length=255, blank=True, null=True)
@ -107,24 +114,21 @@ class CarSpecificationValue(models.Model):
# Car Model # Car Model
class CarStatusChoices(models.TextChoices): class CarStatusChoices(models.TextChoices):
AVAILABLE = 'available', _('Available') AVAILABLE = "available", _("Available")
SOLD = 'sold', _('Sold') SOLD = "sold", _("Sold")
HOLD = 'hold', _('Hold') HOLD = "hold", _("Hold")
DAMAGED = 'damaged', _('Damaged') DAMAGED = "damaged", _("Damaged")
class CarStockTypeChoices(models.TextChoices): class CarStockTypeChoices(models.TextChoices):
NEW = 'new', _('New') NEW = "new", _("New")
USED = 'used', _('Used') USED = "used", _("Used")
class Car(models.Model): class Car(models.Model):
vin = models.CharField(max_length=17, unique=True, verbose_name=_("VIN")) vin = models.CharField(max_length=17, unique=True, verbose_name=_("VIN"))
dealer = models.ForeignKey( dealer = models.ForeignKey(
"Dealer", "Dealer", models.DO_NOTHING, related_name="cars", verbose_name=_("Dealer")
models.DO_NOTHING,
related_name='cars',
verbose_name=_("Dealer")
) )
vendor = models.ForeignKey( vendor = models.ForeignKey(
@ -132,53 +136,53 @@ class Car(models.Model):
models.DO_NOTHING, models.DO_NOTHING,
null=True, null=True,
blank=True, blank=True,
related_name='cars', related_name="cars",
verbose_name=_("Vendor") verbose_name=_("Vendor"),
) )
id_car_make = models.ForeignKey( id_car_make = models.ForeignKey(
CarMake, CarMake,
models.DO_NOTHING, models.DO_NOTHING,
db_column='id_car_make', db_column="id_car_make",
null=True, null=True,
blank=True, blank=True,
verbose_name=_("Make") verbose_name=_("Make"),
) )
id_car_model = models.ForeignKey( id_car_model = models.ForeignKey(
CarModel, CarModel,
models.DO_NOTHING, models.DO_NOTHING,
db_column='id_car_model', db_column="id_car_model",
null=True, null=True,
blank=True, blank=True,
verbose_name=_("Model") verbose_name=_("Model"),
) )
year = models.IntegerField(verbose_name=_("Year")) year = models.IntegerField(verbose_name=_("Year"))
id_car_serie = models.ForeignKey( id_car_serie = models.ForeignKey(
CarSerie, CarSerie,
models.DO_NOTHING, models.DO_NOTHING,
db_column='id_car_serie', db_column="id_car_serie",
null=True, null=True,
blank=True, blank=True,
verbose_name=_("Series") verbose_name=_("Series"),
) )
id_car_trim = models.ForeignKey( id_car_trim = models.ForeignKey(
CarTrim, CarTrim,
models.DO_NOTHING, models.DO_NOTHING,
db_column='id_car_trim', db_column="id_car_trim",
null=True, null=True,
blank=True, blank=True,
verbose_name=_("Trim") verbose_name=_("Trim"),
) )
status = models.CharField( status = models.CharField(
max_length=10, max_length=10,
choices=CarStatusChoices.choices, choices=CarStatusChoices.choices,
default=CarStatusChoices.AVAILABLE, default=CarStatusChoices.AVAILABLE,
verbose_name=_("Status") verbose_name=_("Status"),
) )
stock_type = models.CharField( stock_type = models.CharField(
max_length=10, max_length=10,
choices=CarStockTypeChoices.choices, choices=CarStockTypeChoices.choices,
default=CarStockTypeChoices.NEW, default=CarStockTypeChoices.NEW,
verbose_name=_("Stock Type") verbose_name=_("Stock Type"),
) )
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks")) remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage")) mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
@ -201,21 +205,23 @@ class Car(models.Model):
@property @property
def selling_price(self): def selling_price(self):
finance = self.finances.first() finance = self.finances.first()
return finance.selling_price if finance else Decimal('0.00') return finance.selling_price if finance else Decimal("0.00")
@property @property
def vat_amount(self): def vat_amount(self):
finance = self.finances.first() finance = self.finances.first()
return finance.vat_amount if finance else Decimal('0.00') return finance.vat_amount if finance else Decimal("0.00")
@property @property
def total(self): def total(self):
finance = self.finances.first() finance = self.finances.first()
return finance.total if finance else Decimal('0.00') return finance.total if finance else Decimal("0.00")
class CarReservation(models.Model): class CarReservation(models.Model):
car = models.ForeignKey('Car', on_delete=models.CASCADE, related_name='reservations') car = models.ForeignKey(
"Car", on_delete=models.CASCADE, related_name="reservations"
)
reserved_by = models.ForeignKey(User, on_delete=models.CASCADE) reserved_by = models.ForeignKey(User, on_delete=models.CASCADE)
reserved_at = models.DateTimeField(auto_now_add=True) reserved_at = models.DateTimeField(auto_now_add=True)
reserved_until = models.DateTimeField() reserved_until = models.DateTimeField()
@ -224,19 +230,31 @@ class CarReservation(models.Model):
return self.reserved_until > now() return self.reserved_until > now()
class Meta: class Meta:
unique_together = ('car', 'reserved_until') unique_together = ("car", "reserved_until")
ordering = ['-reserved_at'] ordering = ["-reserved_at"]
# Car Finance Model # Car Finance Model
class CarFinance(models.Model): class CarFinance(models.Model):
car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name='finances') car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name="finances")
cost_price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Cost Price")) cost_price = models.DecimalField(
profit_margin = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("Profit Margin")) max_digits=14, decimal_places=2, verbose_name=_("Cost Price")
selling_price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Selling Price"), editable=False) )
vat_rate = models.DecimalField(max_digits=10, decimal_places=2, default=0.15, verbose_name=_("VAT Rate")) profit_margin = models.DecimalField(
vat_amount = models.DecimalField(max_digits=12, decimal_places=2, verbose_name=_("VAT Amount"), editable=False) max_digits=10, decimal_places=2, verbose_name=_("Profit Margin")
total = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Total Amount"), editable=False) )
selling_price = models.DecimalField(
max_digits=14, decimal_places=2, verbose_name=_("Selling Price"), editable=False
)
vat_rate = models.DecimalField(
max_digits=10, decimal_places=2, default=0.15, verbose_name=_("VAT Rate")
)
vat_amount = models.DecimalField(
max_digits=12, decimal_places=2, verbose_name=_("VAT Amount"), editable=False
)
total = models.DecimalField(
max_digits=14, decimal_places=2, verbose_name=_("Total Amount"), editable=False
)
class Meta: class Meta:
verbose_name = _("Car Financial Details") verbose_name = _("Car Financial Details")
@ -249,16 +267,18 @@ class CarFinance(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__(self): def __str__(self):
return f"Car Financial Details for {self.car}: Selling Price {self.selling_price}" return (
f"Car Financial Details for {self.car}: Selling Price {self.selling_price}"
)
# Colors Model # Colors Model
class CarColors(models.Model): class CarColors(models.Model):
class ColorType(models.TextChoices): class ColorType(models.TextChoices):
EXTERIOR = 'exterior', _("Exterior") EXTERIOR = "exterior", _("Exterior")
INTERIOR = 'interior', _("Interior") INTERIOR = "interior", _("Interior")
car = models.ForeignKey('Car', on_delete=models.CASCADE, related_name='colors') car = models.ForeignKey("Car", on_delete=models.CASCADE, related_name="colors")
name = models.CharField(max_length=255, verbose_name=_("Name")) name = models.CharField(max_length=255, verbose_name=_("Name"))
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
rgb = models.CharField(max_length=24, blank=True, null=True, verbose_name=_("RGB")) rgb = models.CharField(max_length=24, blank=True, null=True, verbose_name=_("RGB"))
@ -266,7 +286,7 @@ class CarColors(models.Model):
max_length=10, max_length=10,
choices=ColorType.choices, choices=ColorType.choices,
default=ColorType.EXTERIOR, default=ColorType.EXTERIOR,
verbose_name=_("Color Type") verbose_name=_("Color Type"),
) )
class Meta: class Meta:
@ -279,7 +299,12 @@ class CarColors(models.Model):
# Custom Card Model # Custom Card Model
class CustomCard(models.Model): class CustomCard(models.Model):
car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name='custom_cards', verbose_name=_("Car")) car = models.ForeignKey(
Car,
on_delete=models.CASCADE,
related_name="custom_cards",
verbose_name=_("Car"),
)
custom_number = models.CharField(max_length=255, verbose_name=_("Custom Number")) custom_number = models.CharField(max_length=255, verbose_name=_("Custom Number"))
custom_date = models.DateField(verbose_name=_("Custom Date")) custom_date = models.DateField(verbose_name=_("Custom Date"))
@ -293,7 +318,12 @@ class CustomCard(models.Model):
# Car Registration Model # Car Registration Model
class CarRegistration(models.Model): class CarRegistration(models.Model):
car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name='registrations', verbose_name=_("Car")) car = models.ForeignKey(
Car,
on_delete=models.CASCADE,
related_name="registrations",
verbose_name=_("Car"),
)
plate_number = models.IntegerField(verbose_name=_("Plate Number")) plate_number = models.IntegerField(verbose_name=_("Plate Number"))
text1 = models.CharField(max_length=1, verbose_name=_("Text 1")) text1 = models.CharField(max_length=1, verbose_name=_("Text 1"))
text2 = models.CharField(max_length=1, verbose_name=_("Text 2")) text2 = models.CharField(max_length=1, verbose_name=_("Text 2"))
@ -319,14 +349,20 @@ class TimestampedModel(models.Model):
# Dealer Model # Dealer Model
class Dealer(models.Model, LocalizedNameMixin): class Dealer(models.Model, LocalizedNameMixin):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='dealer') user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer")
crn = models.CharField(max_length=10, verbose_name=_("Commercial Registration Number")) crn = models.CharField(
max_length=10, verbose_name=_("Commercial Registration Number")
)
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number")) vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
name = models.CharField(max_length=255, verbose_name=_("English Name")) name = models.CharField(max_length=255, verbose_name=_("English Name"))
phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address")) address = models.CharField(
logo = models.ImageField(upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo")) max_length=200, blank=True, null=True, verbose_name=_("Address")
)
logo = models.ImageField(
upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo")
)
class Meta: class Meta:
verbose_name = _("Dealer") verbose_name = _("Dealer")
@ -338,14 +374,20 @@ class Dealer(models.Model, LocalizedNameMixin):
# Vendor Model # Vendor Model
class Vendor(models.Model, LocalizedNameMixin): class Vendor(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='vendors') dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="vendors")
crn = models.CharField(max_length=10, unique=True, verbose_name=_("Commercial Registration Number")) crn = models.CharField(
vrn = models.CharField(max_length=15, unique=True, verbose_name=_("VAT Registration Number")) max_length=10, unique=True, verbose_name=_("Commercial Registration Number")
)
vrn = models.CharField(
max_length=15, unique=True, verbose_name=_("VAT Registration Number")
)
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
name = models.CharField(max_length=255, verbose_name=_("English Name")) name = models.CharField(max_length=255, verbose_name=_("English Name"))
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person")) contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address")) address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address")
)
class Meta: class Meta:
verbose_name = _("Vendor") verbose_name = _("Vendor")
@ -357,14 +399,24 @@ class Vendor(models.Model, LocalizedNameMixin):
# Customer Model # Customer Model
class Customer(models.Model): class Customer(models.Model):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='customers') dealer = models.ForeignKey(
Dealer, on_delete=models.CASCADE, related_name="customers"
)
first_name = models.CharField(max_length=50, verbose_name=_("First Name")) first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
middle_name = models.CharField(max_length=50, blank=True, null=True, verbose_name=_("Middle Name")) middle_name = models.CharField(
max_length=50, blank=True, null=True, verbose_name=_("Middle Name")
)
last_name = models.CharField(max_length=50, verbose_name=_("Last Name")) last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
email = models.EmailField(unique=True, verbose_name=_("Email")) email = models.EmailField(unique=True, verbose_name=_("Email"))
national_id = models.CharField(max_length=10, unique=True, verbose_name=_("National ID")) national_id = models.CharField(
phone_number = PhoneNumberField(region='SA', unique=True, verbose_name=_("Phone Number")) max_length=10, unique=True, verbose_name=_("National ID")
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address")) )
phone_number = PhoneNumberField(
region="SA", unique=True, verbose_name=_("Phone Number")
)
address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address")
)
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
class Meta: class Meta:
@ -372,10 +424,9 @@ class Customer(models.Model):
verbose_name_plural = _("Customers") verbose_name_plural = _("Customers")
def __str__(self): def __str__(self):
middle = f" {self.middle_name}" if self.middle_name else '' middle = f" {self.middle_name}" if self.middle_name else ""
return f"{self.first_name}{middle} {self.last_name}" return f"{self.first_name}{middle} {self.last_name}"
# Create Entity # Create Entity
@receiver(post_save, sender=Dealer) @receiver(post_save, sender=Dealer)
def create_ledger_entity(sender, instance, created, **kwargs): def create_ledger_entity(sender, instance, created, **kwargs):

View File

@ -25,7 +25,6 @@ def decode_vin_pyvin(vin):
print(data) print(data)
return data return data
# vehicle-info # vehicle-info
# c2729afb # c2729afb
# 6d397471920412d672af1b8a02ca52ea # 6d397471920412d672af1b8a02ca52ea
@ -131,7 +130,7 @@ def fetch_colors(car_data):
make = car_data['make'] make = car_data['make']
model = car_data['model'] model = car_data['model']
url = "https://carapi.app/api/exterior-colors?year={}&make={}&model={}".format(year, make, model) url = "https://carapi.app/api/exterior-colors?year={}&make={}&model={}".format(year, make, model)
params = { params = {
'limit': '1000', 'limit': '1000',
'sort': 'name', 'sort': 'name',

View File

@ -62,7 +62,4 @@ urlpatterns = [
path('cars/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'), path('cars/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'),
path('reservations/<int:reservation_id>/', views.manage_reservation, name='reservations'), path('reservations/<int:reservation_id>/', views.manage_reservation, name='reservations'),
path('cars/<int:car_pk>/add-custom-card/', views.CustomCardCreateView.as_view(), name='add_custom_card'), path('cars/<int:car_pk>/add-custom-card/', views.CustomCardCreateView.as_view(), name='add_custom_card'),
]
]

View File

@ -15,7 +15,7 @@ from django.views.generic import (
CreateView, CreateView,
UpdateView, UpdateView,
DeleteView, DeleteView,
TemplateView TemplateView,
) )
from django.utils import timezone, translation from django.utils import timezone, translation
from django.conf import settings from django.conf import settings
@ -34,17 +34,24 @@ logging.basicConfig(level=logging.INFO)
def switch_language(request): def switch_language(request):
language = request.GET.get('language', 'en') language = request.GET.get("language", "en")
referer = request.META.get('HTTP_REFERER', '/') referer = request.META.get("HTTP_REFERER", "/")
parsed_url = urlparse(referer) parsed_url = urlparse(referer)
path_parts = parsed_url.path.split('/') path_parts = parsed_url.path.split("/")
if path_parts[1] in dict(settings.LANGUAGES): if path_parts[1] in dict(settings.LANGUAGES):
path_parts.pop(1) path_parts.pop(1)
new_path = '/'.join(path_parts) new_path = "/".join(path_parts)
new_url = urlunparse( new_url = urlunparse(
(parsed_url.scheme, parsed_url.netloc, new_path, parsed_url.params, parsed_url.query, parsed_url.fragment) (
parsed_url.scheme,
parsed_url.netloc,
new_path,
parsed_url.params,
parsed_url.query,
parsed_url.fragment,
)
) )
if language in dict(settings.LANGUAGES): if language in dict(settings.LANGUAGES):
@ -53,89 +60,96 @@ def switch_language(request):
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language) response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
translation.activate(language) translation.activate(language)
request.session[settings.LANGUAGE_COOKIE_NAME] = language request.session[settings.LANGUAGE_COOKIE_NAME] = language
logger.debug(f"Language switched to: {language}, Session: {request.session[settings.LANGUAGE_COOKIE_NAME]}") logger.debug(
f"Language switched to: {language}, Session: {request.session[settings.LANGUAGE_COOKIE_NAME]}"
)
return response return response
else: else:
logger.warning(f"Invalid language code: {language}") logger.warning(f"Invalid language code: {language}")
return redirect('/') return redirect("/")
class HomeView(LoginRequiredMixin, TemplateView): class HomeView(LoginRequiredMixin, TemplateView):
template_name = 'index.html' template_name = "index.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not hasattr(request.user, 'dealer') or not request.user.is_authenticated: if not hasattr(request.user, "dealer") or not request.user.is_authenticated:
messages.error(request, _('You are not associated with any dealer.')) messages.error(request, _("You are not associated with any dealer."))
return redirect('welcome') return redirect("welcome")
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
total_cars = models.Car.objects.count() total_cars = models.Car.objects.count()
total_reservations = models.CarReservation.objects.filter(reserved_until__gte=timezone.now()).count() total_reservations = models.CarReservation.objects.filter(
reserved_until__gte=timezone.now()
).count()
stats = models.CarFinance.objects.aggregate( stats = models.CarFinance.objects.aggregate(
total_cost_price=Sum('cost_price'), total_cost_price=Sum("cost_price"),
total_selling_price=Sum('selling_price'), total_selling_price=Sum("selling_price"),
) )
total_cost_price = stats['total_cost_price'] or 0 total_cost_price = stats["total_cost_price"] or 0
total_selling_price = stats['total_selling_price'] or 0 total_selling_price = stats["total_selling_price"] or 0
total_profit = total_selling_price - total_cost_price total_profit = total_selling_price - total_cost_price
context['total_cars'] = total_cars context["total_cars"] = total_cars
context['total_reservations'] = total_reservations context["total_reservations"] = total_reservations
context['total_cost_price'] = total_cost_price context["total_cost_price"] = total_cost_price
context['total_selling_price'] = total_selling_price context["total_selling_price"] = total_selling_price
context['total_profit'] = total_profit context["total_profit"] = total_profit
return context return context
class WelcomeView(TemplateView): class WelcomeView(TemplateView):
template_name = "welcome.html" template_name = "welcome.html"
def dispatch(self, request, *args, **kwargs):
if hasattr(request.user, "dealer") and request.user.is_authenticated:
return redirect("landing_page")
return super().dispatch(request, *args, **kwargs)
class CarCreateView(LoginRequiredMixin, CreateView): class CarCreateView(LoginRequiredMixin, CreateView):
model = models.Car model = models.Car
form_class = forms.CarForm form_class = forms.CarForm
template_name = 'inventory/car_form.html' template_name = "inventory/car_form.html"
success_url = reverse_lazy('inventory_stats') success_url = reverse_lazy("inventory_stats")
def form_valid(self, form): def form_valid(self, form):
form.instance.dealer = self.request.user.dealer form.instance.dealer = self.request.user.dealer
form.save() form.save()
messages.success(self.request, 'Car saved successfully.') messages.success(self.request, "Car saved successfully.")
return super().form_valid(form) return super().form_valid(form)
class AjaxHandlerView(LoginRequiredMixin, View): class AjaxHandlerView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
action = request.GET.get('action') action = request.GET.get("action")
handlers = { handlers = {
'decode_vin': self.decode_vin, "decode_vin": self.decode_vin,
'get_models': self.get_models, "get_models": self.get_models,
'get_series': self.get_series, "get_series": self.get_series,
'get_trims': self.get_trims, "get_trims": self.get_trims,
'get_specifications': self.get_specifications, "get_specifications": self.get_specifications,
} }
handler = handlers.get(action) handler = handlers.get(action)
if handler: if handler:
return handler(request) return handler(request)
else: else:
return JsonResponse({'error': 'Invalid action'}, status=400) return JsonResponse({"error": "Invalid action"}, status=400)
def decode_vin(self, request): def decode_vin(self, request):
vin_no = request.GET.get('vin_no') vin_no = request.GET.get("vin_no")
if not vin_no or len(vin_no.strip()) != 17: if not vin_no or len(vin_no.strip()) != 17:
return JsonResponse({'success': False, 'error': 'Invalid VIN number provided.'}, status=400) return JsonResponse(
{"success": False, "error": "Invalid VIN number provided."}, status=400
)
vin_no = vin_no.strip() vin_no = vin_no.strip()
vin_data = {} vin_data = {}
decoding_method = '' decoding_method = ""
decoding_methods = [ decoding_methods = [("PYVIN", decode_vin_pyvin), ("VIN", VIN), ("ELM", elm)]
('PYVIN', decode_vin_pyvin),
('VIN', VIN),
('ELM', elm)
]
manufacturer_name = model_name_before = model_name = year_model = None manufacturer_name = model_name_before = model_name = year_model = None
@ -143,25 +157,33 @@ class AjaxHandlerView(LoginRequiredMixin, View):
try: try:
vin_info = decode_function(vin_no) vin_info = decode_function(vin_no)
if vin_info: if vin_info:
if method_name == 'PYVIN': if method_name == "PYVIN":
manufacturer_name = vin_info.Make.strip() manufacturer_name = vin_info.Make.strip()
model_name_before = vin_info.Model.strip() model_name_before = vin_info.Model.strip()
year_model = vin_info.ModelYear year_model = vin_info.ModelYear
if not manufacturer_name or not year_model: if not manufacturer_name or not year_model:
raise ValueError('PYVIN returned incomplete data.') raise ValueError("PYVIN returned incomplete data.")
elif method_name == 'VIN': elif method_name == "VIN":
manufacturer_name = vin_info.make.strip() manufacturer_name = vin_info.make.strip()
model_name_before = vin_info.model.strip() model_name_before = vin_info.model.strip()
year_model = vin_info.model_year year_model = vin_info.model_year
if not manufacturer_name or not model_name_before or not year_model: if (
raise ValueError('VIN returned incomplete data.') not manufacturer_name
elif method_name == 'ELM': or not model_name_before
elm_data = vin_info.get('data', {}) or not year_model
manufacturer_name = elm_data.get('maker', '').strip() ):
model_name_before = elm_data.get('model', '').strip() raise ValueError("VIN returned incomplete data.")
year_model = elm_data.get('modelYear', '').strip() elif method_name == "ELM":
if not manufacturer_name or not model_name_before or not year_model: elm_data = vin_info.get("data", {})
raise ValueError('ELM returned incomplete data.') manufacturer_name = elm_data.get("maker", "").strip()
model_name_before = elm_data.get("model", "").strip()
year_model = elm_data.get("modelYear", "").strip()
if (
not manufacturer_name
or not model_name_before
or not year_model
):
raise ValueError("ELM returned incomplete data.")
model_name = normalize_name(model_name_before) model_name = normalize_name(model_name_before)
decoding_method = method_name decoding_method = method_name
print(f"decoded by {method_name}") print(f"decoded by {method_name}")
@ -169,75 +191,101 @@ class AjaxHandlerView(LoginRequiredMixin, View):
else: else:
logger.warning(f"{method_name} returned no data for {vin_no}.") logger.warning(f"{method_name} returned no data for {vin_no}.")
except Exception as e: except Exception as e:
logger.warning(f"VIN decoding with {method_name} failed for {vin_no}: {e}") logger.warning(
f"VIN decoding with {method_name} failed for {vin_no}: {e}"
)
if not manufacturer_name or not model_name or not year_model: if not manufacturer_name or not model_name or not year_model:
return JsonResponse({'success': False, 'error': 'VIN not found in all sources.'}, status=404) return JsonResponse(
{"success": False, "error": "VIN not found in all sources."}, status=404
)
logger.info( logger.info(
f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}" f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}"
) )
car_make = models.CarMake.objects.filter(name__icontains=manufacturer_name).first() car_make = models.CarMake.objects.filter(
name__icontains=manufacturer_name
).first()
if not car_make: if not car_make:
return JsonResponse({'success': False, 'error': 'Manufacturer not found in the database.'}, status=404) return JsonResponse(
vin_data['make_id'] = car_make.id_car_make {"success": False, "error": "Manufacturer not found in the database."},
vin_data['name'] = car_make.name status=404,
vin_data['arabic_name'] = car_make.arabic_name )
vin_data["make_id"] = car_make.id_car_make
vin_data["name"] = car_make.name
vin_data["arabic_name"] = car_make.arabic_name
car_model = models.CarModel.objects.filter(id_car_make=car_make.id_car_make, name__icontains=model_name).first() car_model = models.CarModel.objects.filter(
id_car_make=car_make.id_car_make, name__icontains=model_name
).first()
if not car_model: if not car_model:
return JsonResponse({'success': False, 'error': 'Model not found for the given manufacturer.'}, status=404) return JsonResponse(
{
"success": False,
"error": "Model not found for the given manufacturer.",
},
status=404,
)
vin_data['model_id'] = car_model.id_car_model vin_data["model_id"] = car_model.id_car_model
vin_data['year'] = year_model vin_data["year"] = year_model
return JsonResponse({'success': True, 'data': vin_data}) return JsonResponse({"success": True, "data": vin_data})
def get_models(self, request): def get_models(self, request):
make_id = request.GET.get('make_id') make_id = request.GET.get("make_id")
car_models = models.CarModel.objects.filter(id_car_make=make_id).values('id_car_model', 'name', 'arabic_name') car_models = models.CarModel.objects.filter(id_car_make=make_id).values(
"id_car_model", "name", "arabic_name"
)
return JsonResponse(list(car_models), safe=False) return JsonResponse(list(car_models), safe=False)
def get_series(self, request): def get_series(self, request):
model_id = request.GET.get('model_id') model_id = request.GET.get("model_id")
series = models.CarSerie.objects.filter( series = models.CarSerie.objects.filter(
id_car_model=model_id, id_car_model=model_id,
).values('id_car_serie', 'name', 'arabic_name') ).values("id_car_serie", "name", "arabic_name")
return JsonResponse(list(series), safe=False) return JsonResponse(list(series), safe=False)
def get_trims(self, request): def get_trims(self, request):
serie_id = request.GET.get('serie_id') serie_id = request.GET.get("serie_id")
trims = models.CarTrim.objects.filter( trims = models.CarTrim.objects.filter(id_car_serie=serie_id).values(
id_car_serie=serie_id "id_car_trim", "name", "arabic_name"
).values('id_car_trim', 'name', 'arabic_name') )
return JsonResponse(list(trims), safe=False) return JsonResponse(list(trims), safe=False)
def get_specifications(self, request): def get_specifications(self, request):
trim_id = request.GET.get('trim_id') trim_id = request.GET.get("trim_id")
car_spec_values = models.CarSpecificationValue.objects.filter(id_car_trim=trim_id) car_spec_values = models.CarSpecificationValue.objects.filter(
id_car_trim=trim_id
)
lang = translation.get_language() lang = translation.get_language()
specs_by_parent = {} specs_by_parent = {}
for value in car_spec_values: for value in car_spec_values:
specification = value.id_car_specification specification = value.id_car_specification
parent = specification.id_parent parent = specification.id_parent
parent_id = parent.id_car_specification if parent else 0 parent_id = parent.id_car_specification if parent else 0
if lang == 'ar': if lang == "ar":
parent_name = parent.arabic_name if parent else "Root" parent_name = parent.arabic_name if parent else "Root"
else: else:
parent_name = parent.name if parent else "Root" parent_name = parent.name if parent else "Root"
if parent_id not in specs_by_parent: if parent_id not in specs_by_parent:
specs_by_parent[parent_id] = {'parent_name': parent_name, 'specifications': []} specs_by_parent[parent_id] = {
"parent_name": parent_name,
"specifications": [],
}
spec_data = { spec_data = {
'specification_id': specification.id_car_specification, "specification_id": specification.id_car_specification,
's_name': specification.arabic_name if lang == 'ar' else specification.name, "s_name": specification.arabic_name
's_value': value.value, if lang == "ar"
's_unit': value.unit if value.unit else "", else specification.name,
'trim_name': value.id_car_trim.name "s_value": value.value,
"s_unit": value.unit if value.unit else "",
"trim_name": value.id_car_trim.name,
} }
specs_by_parent[parent_id]['specifications'].append(spec_data) specs_by_parent[parent_id]["specifications"].append(spec_data)
serialized_specs = [ serialized_specs = [
{'parent_name': v['parent_name'], 'specifications': v['specifications']} {"parent_name": v["parent_name"], "specifications": v["specifications"]}
for v in specs_by_parent.values() for v in specs_by_parent.values()
] ]
return JsonResponse(serialized_specs, safe=False) return JsonResponse(serialized_specs, safe=False)
@ -245,22 +293,23 @@ class AjaxHandlerView(LoginRequiredMixin, View):
class CarInventory(LoginRequiredMixin, ListView): class CarInventory(LoginRequiredMixin, ListView):
model = models.Car model = models.Car
home_label = _('inventory') home_label = _("inventory")
template_name = 'inventory/car_inventory.html' template_name = "inventory/car_inventory.html"
context_object_name = 'cars' context_object_name = "cars"
paginate_by = 10 paginate_by = 10
ordering = ['receiving_date'] ordering = ["receiving_date"]
def get_queryset(self, *args, **kwargs): def get_queryset(self, *args, **kwargs):
query = self.request.GET.get('q') query = self.request.GET.get("q")
make_id = self.kwargs['make_id'] make_id = self.kwargs["make_id"]
model_id = self.kwargs['model_id'] model_id = self.kwargs["model_id"]
trim_id = self.kwargs['trim_id'] trim_id = self.kwargs["trim_id"]
cars = models.Car.objects.filter( cars = models.Car.objects.filter(
dealer__user=self.request.user, dealer__user=self.request.user,
id_car_make=make_id, id_car_make=make_id,
id_car_model=model_id, id_car_model=model_id,
id_car_trim=trim_id,).order_by('receiving_date') id_car_trim=trim_id,
).order_by("receiving_date")
if query: if query:
cars = cars.filter(Q(vin__icontains=query)) cars = cars.filter(Q(vin__icontains=query))
@ -268,10 +317,10 @@ class CarInventory(LoginRequiredMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['query'] = self.request.GET.get('q', '') context["query"] = self.request.GET.get("q", "")
context['make_id'] = self.kwargs['make_id'] context["make_id"] = self.kwargs["make_id"]
context['model_id'] = self.kwargs['model_id'] context["model_id"] = self.kwargs["model_id"]
context['trim_id'] = self.kwargs['trim_id'] context["trim_id"] = self.kwargs["trim_id"]
return context return context
@ -282,11 +331,11 @@ def inventory_stats_view(request):
# Annotate total cars by make, model, and trim # Annotate total cars by make, model, and trim
cars = ( cars = (
models.Car.objects.filter(dealer=dealer) models.Car.objects.filter(dealer=dealer)
.select_related('id_car_make', 'id_car_model', 'id_car_trim') .select_related("id_car_make", "id_car_model", "id_car_trim")
.annotate( .annotate(
make_total=Count('id_car_make'), make_total=Count("id_car_make"),
model_total=Count('id_car_model'), model_total=Count("id_car_model"),
trim_total=Count('id_car_trim') trim_total=Count("id_car_trim"),
) )
) )
@ -295,123 +344,128 @@ def inventory_stats_view(request):
for car in cars: for car in cars:
# Make Level # Make Level
make = car.id_car_make make = car.id_car_make
if make.id_car_make not in inventory: if make.id_car_make not in inventory:
inventory[make.id_car_make] = { inventory[make.id_car_make] = {
'make_id': make.id_car_make, "make_id": make.id_car_make,
'make_name': make.get_local_name(), "make_name": make.get_local_name(),
'total_cars': 0, "total_cars": 0,
'models': {} "models": {},
} }
inventory[make.id_car_make]['total_cars'] += 1 inventory[make.id_car_make]["total_cars"] += 1
# Model Level # Model Level
model = car.id_car_model model = car.id_car_model
if model and model.id_car_model not in inventory[make.id_car_make]['models']: if model and model.id_car_model not in inventory[make.id_car_make]["models"]:
inventory[make.id_car_make]['models'][model.id_car_model] = { inventory[make.id_car_make]["models"][model.id_car_model] = {
'model_id': model.id_car_model, "model_id": model.id_car_model,
'model_name': model.get_local_name(), "model_name": model.get_local_name(),
'total_cars': 0, "total_cars": 0,
'trims': {} "trims": {},
} }
inventory[make.id_car_make]['models'][model.id_car_model]['total_cars'] += 1 inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1
# Trim Level # Trim Level
trim = car.id_car_trim trim = car.id_car_trim
if trim and trim.id_car_trim not in inventory[make.id_car_make]['models'][model.id_car_model]['trims']: if (
inventory[make.id_car_make]['models'][model.id_car_model]['trims'][trim.id_car_trim] = { trim
'trim_id': trim.id_car_trim, and trim.id_car_trim
'trim_name': trim.name, not in inventory[make.id_car_make]["models"][model.id_car_model]["trims"]
'total_cars': 0 ):
} inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
inventory[make.id_car_make]['models'][model.id_car_model]['trims'][trim.id_car_trim]['total_cars'] += 1 trim.id_car_trim
] = {"trim_id": trim.id_car_trim, "trim_name": trim.name, "total_cars": 0}
inventory[make.id_car_make]["models"][model.id_car_model]["trims"][
trim.id_car_trim
]["total_cars"] += 1
# Convert to a list for easier template rendering # Convert to a list for easier template rendering
result = { result = {
'total_cars': cars.count(), "total_cars": cars.count(),
'makes': [ "makes": [
{ {
'make_id': make_data['make_id'], "make_id": make_data["make_id"],
'make_name': make_data['make_name'], "make_name": make_data["make_name"],
'total_cars': make_data['total_cars'], "total_cars": make_data["total_cars"],
'models': [ "models": [
{ {
'model_id': model_data['model_id'], "model_id": model_data["model_id"],
'model_name': model_data['model_name'], "model_name": model_data["model_name"],
'total_cars': model_data['total_cars'], "total_cars": model_data["total_cars"],
'trims': list(model_data['trims'].values()) "trims": list(model_data["trims"].values()),
} }
for model_data in make_data['models'].values() for model_data in make_data["models"].values()
] ],
} }
for make_data in inventory.values() for make_data in inventory.values()
] ],
} }
return render(request, 'inventory/inventory_stats.html', {'inventory': result}) return render(request, "inventory/inventory_stats.html", {"inventory": result})
class CarDetailView(LoginRequiredMixin, DetailView): class CarDetailView(LoginRequiredMixin, DetailView):
model = models.Car model = models.Car
template_name = 'inventory/car_detail.html' template_name = "inventory/car_detail.html"
context_object_name = 'car' context_object_name = "car"
class CarFinanceCreateView(LoginRequiredMixin, CreateView): class CarFinanceCreateView(LoginRequiredMixin, CreateView):
model = models.CarFinance model = models.CarFinance
form_class = forms.CarFinanceForm form_class = forms.CarFinanceForm
template_name = 'inventory/car_finance_form.html' template_name = "inventory/car_finance_form.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.car = get_object_or_404(models.Car, pk=self.kwargs['car_pk']) self.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
form.instance.car = self.car form.instance.car = self.car
messages.success(self.request, _('Car finance details saved successfully.')) messages.success(self.request, _("Car finance details saved successfully."))
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse('car_detail', kwargs={'pk': self.car.pk}) return reverse("car_detail", kwargs={"pk": self.car.pk})
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['car'] = self.car context["car"] = self.car
return context return context
class CarFinanceUpdateView(LoginRequiredMixin, UpdateView): class CarFinanceUpdateView(LoginRequiredMixin, UpdateView):
model = models.CarFinance model = models.CarFinance
form_class = forms.CarFinanceForm form_class = forms.CarFinanceForm
template_name = 'inventory/car_finance_form.html' template_name = "inventory/car_finance_form.html"
def form_valid(self, form): def form_valid(self, form):
messages.success(self.request, _('Car finance updated successfully.')) messages.success(self.request, _("Car finance updated successfully."))
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse('car_detail', kwargs={'pk': self.object.car.pk}) return reverse("car_detail", kwargs={"pk": self.object.car.pk})
class CarUpdateView(LoginRequiredMixin, UpdateView): class CarUpdateView(LoginRequiredMixin, UpdateView):
model = models.Car model = models.Car
form_class = forms.CarUpdateForm form_class = forms.CarUpdateForm
template_name = 'inventory/car_edit.html' template_name = "inventory/car_edit.html"
def form_valid(self, form): def form_valid(self, form):
messages.success(self.request, _('Car updated successfully.')) messages.success(self.request, _("Car updated successfully."))
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse('car_detail', kwargs={'pk': self.object.pk}) return reverse("car_detail", kwargs={"pk": self.object.pk})
class CarDeleteView(LoginRequiredMixin, DeleteView): class CarDeleteView(LoginRequiredMixin, DeleteView):
model = models.Car model = models.Car
template_name = 'inventory/car_confirm_delete.html' template_name = "inventory/car_confirm_delete.html"
success_url = reverse_lazy('inventory_stats') success_url = reverse_lazy("inventory_stats")
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
messages.success(request, _('Car deleted successfully.')) messages.success(request, _("Car deleted successfully."))
return super().delete(request, *args, **kwargs) return super().delete(request, *args, **kwargs)
@ -421,26 +475,26 @@ class CustomCardCreateView(LoginRequiredMixin, CreateView):
template_name = "inventory/add_custom_card.html" template_name = "inventory/add_custom_card.html"
def form_valid(self, form): def form_valid(self, form):
car = get_object_or_404(models.Car, pk=self.kwargs['car_pk']) car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
form.instance.car = car form.instance.car = car
return super().form_valid(form) return super().form_valid(form)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['car'] = get_object_or_404(models.Car, pk=self.kwargs['car_pk']) context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
return context return context
def get_success_url(self): def get_success_url(self):
messages.success(self.request, _("Custom Card added successfully.")) messages.success(self.request, _("Custom Card added successfully."))
return reverse_lazy('car_detail', kwargs={'pk': self.kwargs['car_pk']}) return reverse_lazy("car_detail", kwargs={"pk": self.kwargs["car_pk"]})
class CarColorCreateView(LoginRequiredMixin, CreateView): class CarColorCreateView(LoginRequiredMixin, CreateView):
model = models.CarColors model = models.CarColors
template_name = 'inventory/color_palette.html' template_name = "inventory/color_palette.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.car = get_object_or_404(models.Car, pk=self.kwargs['car_pk']) self.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
self.available_colors = self.fetch_available_colors() self.available_colors = self.fetch_available_colors()
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@ -448,41 +502,45 @@ class CarColorCreateView(LoginRequiredMixin, CreateView):
class ColorPickerForm(ModelForm): class ColorPickerForm(ModelForm):
color = ChoiceField( color = ChoiceField(
choices=self.get_color_choices(), choices=self.get_color_choices(),
widget=RadioSelect(attrs={'class': 'color-picker'}), widget=RadioSelect(attrs={"class": "color-picker"}),
label=_("Select a Color"), label=_("Select a Color"),
) )
color_type = ChoiceField( color_type = ChoiceField(
choices=models.CarColors.ColorType.choices, choices=models.CarColors.ColorType.choices,
widget=RadioSelect(attrs={'class': 'color-type-picker'}), widget=RadioSelect(attrs={"class": "color-type-picker"}),
label=_("Select Color Type"), label=_("Select Color Type"),
) )
class Meta: class Meta:
model = models.CarColors model = models.CarColors
fields = ['color', 'color_type'] fields = ["color", "color_type"]
return ColorPickerForm return ColorPickerForm
def fetch_available_colors(self): def fetch_available_colors(self):
car_data = { car_data = {
'make': self.car.id_car_make.name, "make": self.car.id_car_make.name,
'model': self.car.id_car_model.name, "model": self.car.id_car_model.name,
'year': str(self.car.year), "year": str(self.car.year),
} }
return fetch_colors(car_data) or [] return fetch_colors(car_data) or []
def get_color_choices(self): def get_color_choices(self):
return [(color['rgb'], color['name']) for color in self.available_colors] return [(color["rgb"], color["name"]) for color in self.available_colors]
def form_valid(self, form): def form_valid(self, form):
selected_rgb = form.cleaned_data['color'] selected_rgb = form.cleaned_data["color"]
selected_name = next( selected_name = next(
(color['name'] for color in self.available_colors if color['rgb'] == selected_rgb), (
None color["name"]
for color in self.available_colors
if color["rgb"] == selected_rgb
),
None,
) )
if not selected_name: if not selected_name:
messages.error(self.request, _('Invalid color selection.')) messages.error(self.request, _("Invalid color selection."))
return self.form_invalid(form) return self.form_invalid(form)
# Assign the car and selected color details # Assign the car and selected color details
@ -490,26 +548,26 @@ class CarColorCreateView(LoginRequiredMixin, CreateView):
form.instance.rgb = selected_rgb form.instance.rgb = selected_rgb
form.instance.name = selected_name form.instance.name = selected_name
form.instance.arabic_name = translate(selected_name) form.instance.arabic_name = translate(selected_name)
form.instance.color_type = form.cleaned_data['color_type'] form.instance.color_type = form.cleaned_data["color_type"]
messages.success(self.request, _('Color added successfully.')) messages.success(self.request, _("Color added successfully."))
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse('car_detail', kwargs={'pk': self.car.pk}) return reverse("car_detail", kwargs={"pk": self.car.pk})
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['car'] = self.car context["car"] = self.car
return context return context
class CarColorUpdateView(LoginRequiredMixin, UpdateView): class CarColorUpdateView(LoginRequiredMixin, UpdateView):
model = forms.CarColors model = forms.CarColors
template_name = 'inventory/color_palette.html' template_name = "inventory/color_palette.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.car = get_object_or_404(models.Car, pk=self.kwargs['car_pk']) self.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
self.available_colors = self.fetch_available_colors() self.available_colors = self.fetch_available_colors()
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@ -517,50 +575,55 @@ class CarColorUpdateView(LoginRequiredMixin, UpdateView):
class ColorPickerForm(ModelForm): class ColorPickerForm(ModelForm):
color = ChoiceField( color = ChoiceField(
choices=self.get_color_choices(), choices=self.get_color_choices(),
widget=RadioSelect(attrs={'class': 'color-picker'}), widget=RadioSelect(attrs={"class": "color-picker"}),
label=_("Select a Color"), label=_("Select a Color"),
) )
class Meta: class Meta:
model = forms.CarColors model = forms.CarColors
fields = ['color'] fields = ["color"]
return ColorPickerForm return ColorPickerForm
def fetch_available_colors(self): def fetch_available_colors(self):
car_data = { car_data = {
'make': self.car.id_car_make.name, "make": self.car.id_car_make.name,
'model': self.car.id_car_model.name, "model": self.car.id_car_model.name,
'year': str(self.car.year), "year": str(self.car.year),
} }
return fetch_colors(car_data) or [] return fetch_colors(car_data) or []
def get_color_choices(self): def get_color_choices(self):
return [(color['rgb'], color['name']) for color in self.available_colors] return [(color["rgb"], color["name"]) for color in self.available_colors]
def form_valid(self, form): def form_valid(self, form):
selected_rgb = form.cleaned_data['color'] selected_rgb = form.cleaned_data["color"]
selected_name = next( selected_name = next(
(color['name'] for color in self.available_colors if color['rgb'] == selected_rgb), (
None color["name"]
for color in self.available_colors
if color["rgb"] == selected_rgb
),
None,
) )
if not selected_name: if not selected_name:
messages.error(self.request, _('Invalid color selection.')) messages.error(self.request, _("Invalid color selection."))
return self.form_invalid(form) return self.form_invalid(form)
form.instance.rgb = selected_rgb form.instance.rgb = selected_rgb
form.instance.name = selected_name form.instance.name = selected_name
form.instance.arabic_name = translate(selected_name) form.instance.arabic_name = translate(selected_name)
messages.success(self.request, _('Exterior color updated successfully.')) messages.success(self.request, _("Exterior color updated successfully."))
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse('car_detail', kwargs={'pk': self.car.pk}) return reverse("car_detail", kwargs={"pk": self.car.pk})
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['car'] = self.car context["car"] = self.car
return context return context
@ -570,26 +633,28 @@ def reserve_car_view(request, car_id):
car = get_object_or_404(models.Car, pk=car_id) car = get_object_or_404(models.Car, pk=car_id)
if car.is_reserved(): if car.is_reserved():
messages.error(request, _("This car is already reserved.")) messages.error(request, _("This car is already reserved."))
return redirect('car_detail', pk=car.pk) return redirect("car_detail", pk=car.pk)
try: try:
reserved_until = timezone.now() + timezone.timedelta(hours=24) reserved_until = timezone.now() + timezone.timedelta(hours=24)
models.CarReservation.objects.create( models.CarReservation.objects.create(
car=car, car=car, reserved_by=request.user, reserved_until=reserved_until
reserved_by=request.user,
reserved_until=reserved_until
) )
messages.success(request, _("Car reserved successfully.")) messages.success(request, _("Car reserved successfully."))
except Exception as e: except Exception as e:
messages.error(request, f"Error reserving car: {e}") messages.error(request, f"Error reserving car: {e}")
return redirect('car_detail', pk=car.pk) return redirect("car_detail", pk=car.pk)
return JsonResponse({"success": False, "message": "Invalid request method."}, status=400) return JsonResponse(
{"success": False, "message": "Invalid request method."}, status=400
)
@login_required @login_required
def manage_reservation(request, reservation_id): def manage_reservation(request, reservation_id):
reservation = get_object_or_404(models.CarReservation, pk=reservation_id, reserved_by=request.user) reservation = get_object_or_404(
models.CarReservation, pk=reservation_id, reserved_by=request.user
)
if request.method == "POST": if request.method == "POST":
action = request.POST.get("action") action = request.POST.get("action")
@ -597,105 +662,109 @@ def manage_reservation(request, reservation_id):
reservation.reserved_until = timezone.now() + timezone.timedelta(hours=24) reservation.reserved_until = timezone.now() + timezone.timedelta(hours=24)
reservation.save() reservation.save()
messages.success(request, _("Reservation renewed successfully.")) messages.success(request, _("Reservation renewed successfully."))
return redirect('car_detail', pk=reservation.car.pk) return redirect("car_detail", pk=reservation.car.pk)
elif action == "cancel": elif action == "cancel":
reservation.delete() reservation.delete()
messages.success(request, _("Reservation canceled successfully.")) messages.success(request, _("Reservation canceled successfully."))
return redirect('car_detail', pk=reservation.car.pk) return redirect("car_detail", pk=reservation.car.pk)
else: else:
return JsonResponse({"success": False, "message": _("Invalid action.")}, status=400) return JsonResponse(
{"success": False, "message": _("Invalid action.")}, status=400
)
return JsonResponse({"success": False, "message": _("Invalid request method.")}, status=400) return JsonResponse(
{"success": False, "message": _("Invalid request method.")}, status=400
)
class DealerListView(LoginRequiredMixin, ListView): class DealerListView(LoginRequiredMixin, ListView):
model = models.Dealer model = models.Dealer
template_name = 'dealer_list.html' template_name = "dealer_list.html"
context_object_name = 'dealers' context_object_name = "dealers"
class DealerDetailView(LoginRequiredMixin, DetailView): class DealerDetailView(LoginRequiredMixin, DetailView):
model = models.Dealer model = models.Dealer
template_name = 'dealers/dealer_detail.html' template_name = "dealers/dealer_detail.html"
context_object_name = 'dealer' context_object_name = "dealer"
class DealerCreateView(LoginRequiredMixin, CreateView): class DealerCreateView(LoginRequiredMixin, CreateView):
model = models.Dealer model = models.Dealer
form_class = forms.DealerForm form_class = forms.DealerForm
template_name = 'dealer_form.html' template_name = "dealer_form.html"
success_url = reverse_lazy('dealer_list') success_url = reverse_lazy("dealer_list")
def form_valid(self, form): def form_valid(self, form):
messages.success(self.request, _('Dealer created successfully.')) messages.success(self.request, _("Dealer created successfully."))
return super().form_valid(form) return super().form_valid(form)
class DealerUpdateView(LoginRequiredMixin, UpdateView): class DealerUpdateView(LoginRequiredMixin, UpdateView):
model = models.Dealer model = models.Dealer
form_class = forms.DealerForm form_class = forms.DealerForm
template_name = 'dealers/dealer_form.html' template_name = "dealers/dealer_form.html"
success_url = reverse_lazy('dealer_detail') success_url = reverse_lazy("dealer_detail")
def form_valid(self, form): def form_valid(self, form):
messages.success(self.request, _('Dealer updated successfully.')) messages.success(self.request, _("Dealer updated successfully."))
return super().form_valid(form) return super().form_valid(form)
class DealerDeleteView(LoginRequiredMixin, DeleteView): class DealerDeleteView(LoginRequiredMixin, DeleteView):
model = models.Dealer model = models.Dealer
template_name = 'dealer_confirm_delete.html' template_name = "dealer_confirm_delete.html"
success_url = reverse_lazy('dealer_list') success_url = reverse_lazy("dealer_list")
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
messages.success(request, _('Dealer deleted successfully.')) messages.success(request, _("Dealer deleted successfully."))
return super().delete(request, *args, **kwargs) return super().delete(request, *args, **kwargs)
class CustomerListView(LoginRequiredMixin, ListView): class CustomerListView(LoginRequiredMixin, ListView):
model = models.Customer model = models.Customer
home_label = _('customers') home_label = _("customers")
context_object_name = 'customers' context_object_name = "customers"
paginate_by = 10 paginate_by = 10
template_name = "customers/customer_list.html" template_name = "customers/customer_list.html"
def get_queryset(self): def get_queryset(self):
query = self.request.GET.get('q') query = self.request.GET.get("q")
customers = models.Customer.objects.filter(dealer__user=self.request.user) customers = models.Customer.objects.filter(dealer__user=self.request.user)
if query: if query:
customers = customers.filter( customers = customers.filter(
Q(national_id__icontains=query) | Q(national_id__icontains=query)
Q(first_name__icontains=query) | | Q(first_name__icontains=query)
Q(last_name__icontains=query) | Q(last_name__icontains=query)
) )
return customers return customers
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['query'] = self.request.GET.get('q', '') context["query"] = self.request.GET.get("q", "")
return context return context
class CustomerDetailView(LoginRequiredMixin, DetailView): class CustomerDetailView(LoginRequiredMixin, DetailView):
model = models.Customer model = models.Customer
template_name = 'customers/view_customer.html' template_name = "customers/view_customer.html"
context_object_name = 'customer' context_object_name = "customer"
class CustomerCreateView(LoginRequiredMixin, CreateView): class CustomerCreateView(LoginRequiredMixin, CreateView):
model = models.Customer model = models.Customer
form_class = forms.CustomerForm form_class = forms.CustomerForm
template_name = 'customers/customer_form.html' template_name = "customers/customer_form.html"
success_url = reverse_lazy('customer_list') success_url = reverse_lazy("customer_list")
def form_valid(self, form): def form_valid(self, form):
if form.is_valid(): if form.is_valid():
form.instance.dealer = self.request.user.dealer form.instance.dealer = self.request.user.dealer
form.save() form.save()
messages.success(self.request, _('Customer created successfully.')) messages.success(self.request, _("Customer created successfully."))
return super().form_valid(form) return super().form_valid(form)
else: else:
return form.errors return form.errors
@ -704,14 +773,14 @@ class CustomerCreateView(LoginRequiredMixin, CreateView):
class CustomerUpdateView(LoginRequiredMixin, UpdateView): class CustomerUpdateView(LoginRequiredMixin, UpdateView):
model = models.Customer model = models.Customer
form_class = forms.CustomerForm form_class = forms.CustomerForm
template_name = 'customers/customer_form.html' template_name = "customers/customer_form.html"
success_url = reverse_lazy('customer_list') success_url = reverse_lazy("customer_list")
def form_valid(self, form): def form_valid(self, form):
if form.is_valid(): if form.is_valid():
form.instance.dealer = self.request.user.dealer form.instance.dealer = self.request.user.dealer
form.save() form.save()
messages.success(self.request, _('Customer updated successfully.')) messages.success(self.request, _("Customer updated successfully."))
return super().form_valid(form) return super().form_valid(form)
else: else:
return form.errors return form.errors
@ -721,6 +790,7 @@ class CustomerUpdateView(LoginRequiredMixin, UpdateView):
def delete_customer(request, pk): def delete_customer(request, pk):
customer = get_object_or_404(models.Customer, pk=pk) customer = get_object_or_404(models.Customer, pk=pk)
customer.delete() customer.delete()
<<<<<<< HEAD
messages.success(request, _('Customer deleted successfully.')) messages.success(request, _('Customer deleted successfully.'))
return redirect('customer_list') return redirect('customer_list')
@ -778,3 +848,7 @@ def delete_vendor(request, pk):
=======
messages.success(request, _("Customer deleted successfully."))
return redirect("customer_list")
>>>>>>> c96865f (add search to select fields, redirect logged in user to landingpage if they are authenticated)

View File

@ -16,7 +16,7 @@ dill==0.3.9
distro==1.9.0 distro==1.9.0
dj-rest-auth==7.0.0 dj-rest-auth==7.0.0
dj-shop-cart==7.1.1 dj-shop-cart==7.1.1
Django==5.1.4 Django
django-allauth==65.3.0 django-allauth==65.3.0
django-autoslug==1.9.9 django-autoslug==1.9.9
django-bootstrap5==24.3 django-bootstrap5==24.3
@ -74,7 +74,6 @@ platformdirs==4.3.6
prometheus_client==0.21.1 prometheus_client==0.21.1
psycopg==3.2.3 psycopg==3.2.3
psycopg-binary==3.2.3 psycopg-binary==3.2.3
psycopg-c==3.2.3
py-moneyed==3.0 py-moneyed==3.0
pycodestyle==2.12.1 pycodestyle==2.12.1
pycparser==2.22 pycparser==2.22

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -16,10 +16,15 @@
{% endif %} {% endif %}
<link href="{% static 'css/themes/cosmo/_bootswatch.scss' %}" rel="stylesheet" /> <link href="{% static 'css/themes/cosmo/_bootswatch.scss' %}" rel="stylesheet" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ttskch/select2-bootstrap4-theme@x.x.x/dist/select2-bootstrap4.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.10.5/dist/sweetalert2.all.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.10.5/dist/sweetalert2.all.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<style> <style>
body { body {
min-height: 100vh; min-height: 100vh;
@ -45,9 +50,11 @@
<!-- JavaScript Files --> <!-- JavaScript Files -->
<script type="text/javascript" src="{% static 'js/main.js' %}"></script> <script type="text/javascript" src="{% static 'js/main.js' %}"></script>
<script>
<script> document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('DOMContentLoaded', () => { $('.form-select').select2({
theme: 'bootstrap4',
});
'use strict'; 'use strict';
// Fetch all the forms with the "needs-validation" class // Fetch all the forms with the "needs-validation" class

View File

@ -436,6 +436,7 @@ async function decodeVin() {
} }
async function updateFields(vinData) { async function updateFields(vinData) {
console.log(vinData)
if (vinData.make_id) { if (vinData.make_id) {
makeSelect.value = vinData.make_id; makeSelect.value = vinData.make_id;
await loadModels(vinData.make_id); await loadModels(vinData.make_id);
@ -556,7 +557,7 @@ async function loadSpecifications(trimId){
'X-CSRFToken': csrfToken 'X-CSRFToken': csrfToken
} }
}); });
const data = await response.json(); const data = await response.json();
data.forEach((spec) => { data.forEach((spec) => {
const parentDiv = document.createElement('div'); const parentDiv = document.createElement('div');
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`; parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
@ -585,7 +586,7 @@ async function loadSpecifications(trimId){
}); });
trimSelect.addEventListener('change', () => { trimSelect.addEventListener('change', () => {
const trimId = trimSelect.value; const trimId = trimSelect.value;
showSpecificationButton.disabled = !trimId; showSpecificationButton.disabled = !trimId;
if (trimId) loadSpecifications(trimId); if (trimId) loadSpecifications(trimId);
}); });

View File

@ -113,5 +113,4 @@
</main> </main>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
get_stats

View File

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load custom_filters %} {% load custom_filters %}