add search to select fields, redirect logged in user to landingpage if they are authenticated
This commit is contained in:
parent
9eba42aa91
commit
2dfbb5fa9a
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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!
|
||||
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
|
||||
|
||||
@ -110,9 +110,9 @@ WSGI_APPLICATION = 'car_inventory.wsgi.application'
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django_prometheus.db.backends.postgresql",
|
||||
"NAME": "secondhaikal",
|
||||
"USER": "f95166",
|
||||
"PASSWORD": "Kfsh&rc9788",
|
||||
"NAME": "haikal",
|
||||
"USER": "haikal",
|
||||
"PASSWORD": "haikal",
|
||||
"HOST": "localhost",
|
||||
"PORT": 5432,
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ admin.site.register(models.CarRegistration)
|
||||
admin.site.register(models.CustomCard)
|
||||
admin.site.register(models.CarSpecificationValue)
|
||||
|
||||
|
||||
@admin.register(models.CarMake)
|
||||
class CarMakeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'arabic_name', 'is_sa_import')
|
||||
@ -51,18 +50,18 @@ class CarSeriesAdmin(admin.ModelAdmin):
|
||||
verbose_name = "Car Series"
|
||||
|
||||
|
||||
@admin.register(models.CarTrim)
|
||||
class CarTrimAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',
|
||||
'id_car_serie__name',
|
||||
'id_car_serie__id_car_model__name',
|
||||
'id_car_serie__id_car_model__id_car_make__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',
|
||||
'id_car_serie__id_car_model__id_car_make__name')
|
||||
# @admin.register(models.CarTrim)
|
||||
# class CarTrimAdmin(admin.ModelAdmin):
|
||||
# list_display = ('name',
|
||||
# 'id_car_serie__name',
|
||||
# 'id_car_serie__id_car_model__name',
|
||||
# 'id_car_serie__id_car_model__id_car_make__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',
|
||||
# 'id_car_serie__id_car_model__id_car_make__name')
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Car Trim"
|
||||
# class Meta:
|
||||
# verbose_name = "Car Trim"
|
||||
|
||||
|
||||
@admin.register(models.CarSpecification)
|
||||
|
||||
37
inventory/management/commands/seed_customer.py
Normal file
37
inventory/management/commands/seed_customer.py
Normal 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.'))
|
||||
@ -14,7 +14,6 @@ from django_ledger.models import (
|
||||
UnitOfMeasureModel,
|
||||
CustomerModel,
|
||||
ItemModelQuerySet,
|
||||
|
||||
)
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@ -27,7 +26,7 @@ class CarMake(models.Model, LocalizedNameMixin):
|
||||
id_car_make = models.AutoField(primary_key=True)
|
||||
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)
|
||||
|
||||
def __str__(self):
|
||||
@ -39,7 +38,7 @@ class CarMake(models.Model, LocalizedNameMixin):
|
||||
|
||||
class CarModel(models.Model, LocalizedNameMixin):
|
||||
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)
|
||||
arabic_name = models.CharField(max_length=255)
|
||||
|
||||
@ -52,7 +51,9 @@ class CarModel(models.Model, LocalizedNameMixin):
|
||||
|
||||
class CarSerie(models.Model, LocalizedNameMixin):
|
||||
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)
|
||||
arabic_name = models.CharField(max_length=255)
|
||||
|
||||
@ -65,7 +66,9 @@ class CarSerie(models.Model, LocalizedNameMixin):
|
||||
|
||||
class CarTrim(models.Model, LocalizedNameMixin):
|
||||
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)
|
||||
arabic_name = models.CharField(max_length=255)
|
||||
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)
|
||||
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):
|
||||
return self.name
|
||||
@ -93,8 +98,10 @@ class CarSpecification(models.Model, LocalizedNameMixin):
|
||||
|
||||
class CarSpecificationValue(models.Model):
|
||||
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_specification = models.ForeignKey(CarSpecification, models.DO_NOTHING, db_column='id_car_specification')
|
||||
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"
|
||||
)
|
||||
value = models.CharField(max_length=500)
|
||||
unit = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
@ -107,24 +114,21 @@ class CarSpecificationValue(models.Model):
|
||||
|
||||
# Car Model
|
||||
class CarStatusChoices(models.TextChoices):
|
||||
AVAILABLE = 'available', _('Available')
|
||||
SOLD = 'sold', _('Sold')
|
||||
HOLD = 'hold', _('Hold')
|
||||
DAMAGED = 'damaged', _('Damaged')
|
||||
AVAILABLE = "available", _("Available")
|
||||
SOLD = "sold", _("Sold")
|
||||
HOLD = "hold", _("Hold")
|
||||
DAMAGED = "damaged", _("Damaged")
|
||||
|
||||
|
||||
class CarStockTypeChoices(models.TextChoices):
|
||||
NEW = 'new', _('New')
|
||||
USED = 'used', _('Used')
|
||||
NEW = "new", _("New")
|
||||
USED = "used", _("Used")
|
||||
|
||||
|
||||
class Car(models.Model):
|
||||
vin = models.CharField(max_length=17, unique=True, verbose_name=_("VIN"))
|
||||
dealer = models.ForeignKey(
|
||||
"Dealer",
|
||||
models.DO_NOTHING,
|
||||
related_name='cars',
|
||||
verbose_name=_("Dealer")
|
||||
"Dealer", models.DO_NOTHING, related_name="cars", verbose_name=_("Dealer")
|
||||
)
|
||||
|
||||
vendor = models.ForeignKey(
|
||||
@ -132,53 +136,53 @@ class Car(models.Model):
|
||||
models.DO_NOTHING,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='cars',
|
||||
verbose_name=_("Vendor")
|
||||
related_name="cars",
|
||||
verbose_name=_("Vendor"),
|
||||
)
|
||||
id_car_make = models.ForeignKey(
|
||||
CarMake,
|
||||
models.DO_NOTHING,
|
||||
db_column='id_car_make',
|
||||
db_column="id_car_make",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("Make")
|
||||
verbose_name=_("Make"),
|
||||
)
|
||||
id_car_model = models.ForeignKey(
|
||||
CarModel,
|
||||
models.DO_NOTHING,
|
||||
db_column='id_car_model',
|
||||
db_column="id_car_model",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("Model")
|
||||
verbose_name=_("Model"),
|
||||
)
|
||||
year = models.IntegerField(verbose_name=_("Year"))
|
||||
id_car_serie = models.ForeignKey(
|
||||
CarSerie,
|
||||
models.DO_NOTHING,
|
||||
db_column='id_car_serie',
|
||||
db_column="id_car_serie",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("Series")
|
||||
verbose_name=_("Series"),
|
||||
)
|
||||
id_car_trim = models.ForeignKey(
|
||||
CarTrim,
|
||||
models.DO_NOTHING,
|
||||
db_column='id_car_trim',
|
||||
db_column="id_car_trim",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("Trim")
|
||||
verbose_name=_("Trim"),
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=10,
|
||||
choices=CarStatusChoices.choices,
|
||||
default=CarStatusChoices.AVAILABLE,
|
||||
verbose_name=_("Status")
|
||||
verbose_name=_("Status"),
|
||||
)
|
||||
stock_type = models.CharField(
|
||||
max_length=10,
|
||||
choices=CarStockTypeChoices.choices,
|
||||
default=CarStockTypeChoices.NEW,
|
||||
verbose_name=_("Stock Type")
|
||||
verbose_name=_("Stock Type"),
|
||||
)
|
||||
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
|
||||
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
|
||||
@ -201,21 +205,23 @@ class Car(models.Model):
|
||||
@property
|
||||
def selling_price(self):
|
||||
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
|
||||
def vat_amount(self):
|
||||
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
|
||||
def total(self):
|
||||
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):
|
||||
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_at = models.DateTimeField(auto_now_add=True)
|
||||
reserved_until = models.DateTimeField()
|
||||
@ -224,19 +230,31 @@ class CarReservation(models.Model):
|
||||
return self.reserved_until > now()
|
||||
|
||||
class Meta:
|
||||
unique_together = ('car', 'reserved_until')
|
||||
ordering = ['-reserved_at']
|
||||
unique_together = ("car", "reserved_until")
|
||||
ordering = ["-reserved_at"]
|
||||
|
||||
|
||||
# Car Finance Model
|
||||
class CarFinance(models.Model):
|
||||
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"))
|
||||
profit_margin = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("Profit Margin"))
|
||||
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)
|
||||
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")
|
||||
)
|
||||
profit_margin = models.DecimalField(
|
||||
max_digits=10, decimal_places=2, verbose_name=_("Profit Margin")
|
||||
)
|
||||
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:
|
||||
verbose_name = _("Car Financial Details")
|
||||
@ -249,16 +267,18 @@ class CarFinance(models.Model):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
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
|
||||
class CarColors(models.Model):
|
||||
class ColorType(models.TextChoices):
|
||||
EXTERIOR = 'exterior', _("Exterior")
|
||||
INTERIOR = 'interior', _("Interior")
|
||||
EXTERIOR = "exterior", _("Exterior")
|
||||
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"))
|
||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||
rgb = models.CharField(max_length=24, blank=True, null=True, verbose_name=_("RGB"))
|
||||
@ -266,7 +286,7 @@ class CarColors(models.Model):
|
||||
max_length=10,
|
||||
choices=ColorType.choices,
|
||||
default=ColorType.EXTERIOR,
|
||||
verbose_name=_("Color Type")
|
||||
verbose_name=_("Color Type"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -279,7 +299,12 @@ class CarColors(models.Model):
|
||||
|
||||
# Custom Card 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_date = models.DateField(verbose_name=_("Custom Date"))
|
||||
|
||||
@ -293,7 +318,12 @@ class CustomCard(models.Model):
|
||||
|
||||
# Car Registration 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"))
|
||||
text1 = models.CharField(max_length=1, verbose_name=_("Text 1"))
|
||||
text2 = models.CharField(max_length=1, verbose_name=_("Text 2"))
|
||||
@ -319,14 +349,20 @@ class TimestampedModel(models.Model):
|
||||
|
||||
# Dealer Model
|
||||
class Dealer(models.Model, LocalizedNameMixin):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='dealer')
|
||||
crn = models.CharField(max_length=10, verbose_name=_("Commercial Registration Number"))
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer")
|
||||
crn = models.CharField(
|
||||
max_length=10, verbose_name=_("Commercial Registration Number")
|
||||
)
|
||||
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
|
||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
||||
phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number"))
|
||||
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address"))
|
||||
logo = models.ImageField(upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo"))
|
||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
||||
address = models.CharField(
|
||||
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:
|
||||
verbose_name = _("Dealer")
|
||||
@ -338,14 +374,20 @@ class Dealer(models.Model, LocalizedNameMixin):
|
||||
|
||||
# Vendor Model
|
||||
class Vendor(models.Model, LocalizedNameMixin):
|
||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='vendors')
|
||||
crn = models.CharField(max_length=10, unique=True, verbose_name=_("Commercial Registration Number"))
|
||||
vrn = models.CharField(max_length=15, unique=True, verbose_name=_("VAT Registration Number"))
|
||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="vendors")
|
||||
crn = models.CharField(
|
||||
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"))
|
||||
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
||||
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
|
||||
phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number"))
|
||||
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address"))
|
||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
||||
address = models.CharField(
|
||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Vendor")
|
||||
@ -357,14 +399,24 @@ class Vendor(models.Model, LocalizedNameMixin):
|
||||
|
||||
# Customer 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"))
|
||||
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"))
|
||||
email = models.EmailField(unique=True, verbose_name=_("Email"))
|
||||
national_id = models.CharField(max_length=10, unique=True, verbose_name=_("National ID"))
|
||||
phone_number = PhoneNumberField(region='SA', unique=True, verbose_name=_("Phone Number"))
|
||||
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address"))
|
||||
national_id = models.CharField(
|
||||
max_length=10, unique=True, verbose_name=_("National ID")
|
||||
)
|
||||
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"))
|
||||
|
||||
class Meta:
|
||||
@ -372,10 +424,9 @@ class Customer(models.Model):
|
||||
verbose_name_plural = _("Customers")
|
||||
|
||||
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}"
|
||||
|
||||
|
||||
# Create Entity
|
||||
@receiver(post_save, sender=Dealer)
|
||||
def create_ledger_entity(sender, instance, created, **kwargs):
|
||||
|
||||
@ -25,7 +25,6 @@ def decode_vin_pyvin(vin):
|
||||
print(data)
|
||||
return data
|
||||
|
||||
|
||||
# vehicle-info
|
||||
# c2729afb
|
||||
# 6d397471920412d672af1b8a02ca52ea
|
||||
@ -131,7 +130,7 @@ def fetch_colors(car_data):
|
||||
make = car_data['make']
|
||||
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 = {
|
||||
'limit': '1000',
|
||||
'sort': 'name',
|
||||
|
||||
@ -62,7 +62,4 @@ urlpatterns = [
|
||||
path('cars/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'),
|
||||
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'),
|
||||
|
||||
]
|
||||
|
||||
|
||||
]
|
||||
@ -15,7 +15,7 @@ from django.views.generic import (
|
||||
CreateView,
|
||||
UpdateView,
|
||||
DeleteView,
|
||||
TemplateView
|
||||
TemplateView,
|
||||
)
|
||||
from django.utils import timezone, translation
|
||||
from django.conf import settings
|
||||
@ -34,17 +34,24 @@ logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def switch_language(request):
|
||||
language = request.GET.get('language', 'en')
|
||||
referer = request.META.get('HTTP_REFERER', '/')
|
||||
language = request.GET.get("language", "en")
|
||||
referer = request.META.get("HTTP_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):
|
||||
path_parts.pop(1)
|
||||
|
||||
new_path = '/'.join(path_parts)
|
||||
new_path = "/".join(path_parts)
|
||||
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):
|
||||
@ -53,89 +60,96 @@ def switch_language(request):
|
||||
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
|
||||
translation.activate(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
|
||||
else:
|
||||
logger.warning(f"Invalid language code: {language}")
|
||||
return redirect('/')
|
||||
return redirect("/")
|
||||
|
||||
|
||||
class HomeView(LoginRequiredMixin, TemplateView):
|
||||
template_name = 'index.html'
|
||||
template_name = "index.html"
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not hasattr(request.user, 'dealer') or not request.user.is_authenticated:
|
||||
messages.error(request, _('You are not associated with any dealer.'))
|
||||
return redirect('welcome')
|
||||
if not hasattr(request.user, "dealer") or not request.user.is_authenticated:
|
||||
messages.error(request, _("You are not associated with any dealer."))
|
||||
return redirect("welcome")
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
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(
|
||||
total_cost_price=Sum('cost_price'),
|
||||
total_selling_price=Sum('selling_price'),
|
||||
total_cost_price=Sum("cost_price"),
|
||||
total_selling_price=Sum("selling_price"),
|
||||
)
|
||||
total_cost_price = stats['total_cost_price'] or 0
|
||||
total_selling_price = stats['total_selling_price'] or 0
|
||||
total_cost_price = stats["total_cost_price"] or 0
|
||||
total_selling_price = stats["total_selling_price"] or 0
|
||||
total_profit = total_selling_price - total_cost_price
|
||||
|
||||
context['total_cars'] = total_cars
|
||||
context['total_reservations'] = total_reservations
|
||||
context['total_cost_price'] = total_cost_price
|
||||
context['total_selling_price'] = total_selling_price
|
||||
context['total_profit'] = total_profit
|
||||
context["total_cars"] = total_cars
|
||||
context["total_reservations"] = total_reservations
|
||||
context["total_cost_price"] = total_cost_price
|
||||
context["total_selling_price"] = total_selling_price
|
||||
context["total_profit"] = total_profit
|
||||
return context
|
||||
|
||||
|
||||
class WelcomeView(TemplateView):
|
||||
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):
|
||||
model = models.Car
|
||||
form_class = forms.CarForm
|
||||
template_name = 'inventory/car_form.html'
|
||||
success_url = reverse_lazy('inventory_stats')
|
||||
template_name = "inventory/car_form.html"
|
||||
success_url = reverse_lazy("inventory_stats")
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.dealer = self.request.user.dealer
|
||||
form.save()
|
||||
messages.success(self.request, 'Car saved successfully.')
|
||||
messages.success(self.request, "Car saved successfully.")
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class AjaxHandlerView(LoginRequiredMixin, View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
action = request.GET.get('action')
|
||||
action = request.GET.get("action")
|
||||
handlers = {
|
||||
'decode_vin': self.decode_vin,
|
||||
'get_models': self.get_models,
|
||||
'get_series': self.get_series,
|
||||
'get_trims': self.get_trims,
|
||||
'get_specifications': self.get_specifications,
|
||||
"decode_vin": self.decode_vin,
|
||||
"get_models": self.get_models,
|
||||
"get_series": self.get_series,
|
||||
"get_trims": self.get_trims,
|
||||
"get_specifications": self.get_specifications,
|
||||
}
|
||||
handler = handlers.get(action)
|
||||
if handler:
|
||||
return handler(request)
|
||||
else:
|
||||
return JsonResponse({'error': 'Invalid action'}, status=400)
|
||||
return JsonResponse({"error": "Invalid action"}, status=400)
|
||||
|
||||
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:
|
||||
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_data = {}
|
||||
decoding_method = ''
|
||||
decoding_method = ""
|
||||
|
||||
decoding_methods = [
|
||||
('PYVIN', decode_vin_pyvin),
|
||||
('VIN', VIN),
|
||||
('ELM', elm)
|
||||
]
|
||||
decoding_methods = [("PYVIN", decode_vin_pyvin), ("VIN", VIN), ("ELM", elm)]
|
||||
|
||||
manufacturer_name = model_name_before = model_name = year_model = None
|
||||
|
||||
@ -143,25 +157,33 @@ class AjaxHandlerView(LoginRequiredMixin, View):
|
||||
try:
|
||||
vin_info = decode_function(vin_no)
|
||||
if vin_info:
|
||||
if method_name == 'PYVIN':
|
||||
if method_name == "PYVIN":
|
||||
manufacturer_name = vin_info.Make.strip()
|
||||
model_name_before = vin_info.Model.strip()
|
||||
year_model = vin_info.ModelYear
|
||||
if not manufacturer_name or not year_model:
|
||||
raise ValueError('PYVIN returned incomplete data.')
|
||||
elif method_name == 'VIN':
|
||||
raise ValueError("PYVIN returned incomplete data.")
|
||||
elif method_name == "VIN":
|
||||
manufacturer_name = vin_info.make.strip()
|
||||
model_name_before = vin_info.model.strip()
|
||||
year_model = vin_info.model_year
|
||||
if not manufacturer_name or not model_name_before or not year_model:
|
||||
raise ValueError('VIN returned incomplete data.')
|
||||
elif method_name == 'ELM':
|
||||
elm_data = vin_info.get('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.')
|
||||
if (
|
||||
not manufacturer_name
|
||||
or not model_name_before
|
||||
or not year_model
|
||||
):
|
||||
raise ValueError("VIN returned incomplete data.")
|
||||
elif method_name == "ELM":
|
||||
elm_data = vin_info.get("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)
|
||||
decoding_method = method_name
|
||||
print(f"decoded by {method_name}")
|
||||
@ -169,75 +191,101 @@ class AjaxHandlerView(LoginRequiredMixin, View):
|
||||
else:
|
||||
logger.warning(f"{method_name} returned no data for {vin_no}.")
|
||||
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:
|
||||
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(
|
||||
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:
|
||||
return JsonResponse({'success': False, 'error': 'Manufacturer not found in the database.'}, status=404)
|
||||
vin_data['make_id'] = car_make.id_car_make
|
||||
vin_data['name'] = car_make.name
|
||||
vin_data['arabic_name'] = car_make.arabic_name
|
||||
return JsonResponse(
|
||||
{"success": False, "error": "Manufacturer not found in the database."},
|
||||
status=404,
|
||||
)
|
||||
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:
|
||||
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['year'] = year_model
|
||||
return JsonResponse({'success': True, 'data': vin_data})
|
||||
vin_data["model_id"] = car_model.id_car_model
|
||||
vin_data["year"] = year_model
|
||||
return JsonResponse({"success": True, "data": vin_data})
|
||||
|
||||
def get_models(self, request):
|
||||
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')
|
||||
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"
|
||||
)
|
||||
return JsonResponse(list(car_models), safe=False)
|
||||
|
||||
def get_series(self, request):
|
||||
model_id = request.GET.get('model_id')
|
||||
model_id = request.GET.get("model_id")
|
||||
series = models.CarSerie.objects.filter(
|
||||
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)
|
||||
|
||||
def get_trims(self, request):
|
||||
serie_id = request.GET.get('serie_id')
|
||||
trims = models.CarTrim.objects.filter(
|
||||
id_car_serie=serie_id
|
||||
).values('id_car_trim', 'name', 'arabic_name')
|
||||
serie_id = request.GET.get("serie_id")
|
||||
trims = models.CarTrim.objects.filter(id_car_serie=serie_id).values(
|
||||
"id_car_trim", "name", "arabic_name"
|
||||
)
|
||||
return JsonResponse(list(trims), safe=False)
|
||||
|
||||
def get_specifications(self, request):
|
||||
trim_id = request.GET.get('trim_id')
|
||||
car_spec_values = models.CarSpecificationValue.objects.filter(id_car_trim=trim_id)
|
||||
trim_id = request.GET.get("trim_id")
|
||||
car_spec_values = models.CarSpecificationValue.objects.filter(
|
||||
id_car_trim=trim_id
|
||||
)
|
||||
lang = translation.get_language()
|
||||
specs_by_parent = {}
|
||||
for value in car_spec_values:
|
||||
specification = value.id_car_specification
|
||||
parent = specification.id_parent
|
||||
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"
|
||||
else:
|
||||
parent_name = parent.name if parent else "Root"
|
||||
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 = {
|
||||
'specification_id': specification.id_car_specification,
|
||||
's_name': specification.arabic_name if lang == 'ar' else specification.name,
|
||||
's_value': value.value,
|
||||
's_unit': value.unit if value.unit else "",
|
||||
'trim_name': value.id_car_trim.name
|
||||
"specification_id": specification.id_car_specification,
|
||||
"s_name": specification.arabic_name
|
||||
if lang == "ar"
|
||||
else specification.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 = [
|
||||
{'parent_name': v['parent_name'], 'specifications': v['specifications']}
|
||||
{"parent_name": v["parent_name"], "specifications": v["specifications"]}
|
||||
for v in specs_by_parent.values()
|
||||
]
|
||||
return JsonResponse(serialized_specs, safe=False)
|
||||
@ -245,22 +293,23 @@ class AjaxHandlerView(LoginRequiredMixin, View):
|
||||
|
||||
class CarInventory(LoginRequiredMixin, ListView):
|
||||
model = models.Car
|
||||
home_label = _('inventory')
|
||||
template_name = 'inventory/car_inventory.html'
|
||||
context_object_name = 'cars'
|
||||
home_label = _("inventory")
|
||||
template_name = "inventory/car_inventory.html"
|
||||
context_object_name = "cars"
|
||||
paginate_by = 10
|
||||
ordering = ['receiving_date']
|
||||
ordering = ["receiving_date"]
|
||||
|
||||
def get_queryset(self, *args, **kwargs):
|
||||
query = self.request.GET.get('q')
|
||||
make_id = self.kwargs['make_id']
|
||||
model_id = self.kwargs['model_id']
|
||||
trim_id = self.kwargs['trim_id']
|
||||
query = self.request.GET.get("q")
|
||||
make_id = self.kwargs["make_id"]
|
||||
model_id = self.kwargs["model_id"]
|
||||
trim_id = self.kwargs["trim_id"]
|
||||
cars = models.Car.objects.filter(
|
||||
dealer__user=self.request.user,
|
||||
id_car_make=make_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:
|
||||
cars = cars.filter(Q(vin__icontains=query))
|
||||
@ -268,10 +317,10 @@ class CarInventory(LoginRequiredMixin, ListView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['query'] = self.request.GET.get('q', '')
|
||||
context['make_id'] = self.kwargs['make_id']
|
||||
context['model_id'] = self.kwargs['model_id']
|
||||
context['trim_id'] = self.kwargs['trim_id']
|
||||
context["query"] = self.request.GET.get("q", "")
|
||||
context["make_id"] = self.kwargs["make_id"]
|
||||
context["model_id"] = self.kwargs["model_id"]
|
||||
context["trim_id"] = self.kwargs["trim_id"]
|
||||
return context
|
||||
|
||||
|
||||
@ -282,11 +331,11 @@ def inventory_stats_view(request):
|
||||
# Annotate total cars by make, model, and trim
|
||||
cars = (
|
||||
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(
|
||||
make_total=Count('id_car_make'),
|
||||
model_total=Count('id_car_model'),
|
||||
trim_total=Count('id_car_trim')
|
||||
make_total=Count("id_car_make"),
|
||||
model_total=Count("id_car_model"),
|
||||
trim_total=Count("id_car_trim"),
|
||||
)
|
||||
)
|
||||
|
||||
@ -295,123 +344,128 @@ def inventory_stats_view(request):
|
||||
for car in cars:
|
||||
# Make Level
|
||||
make = car.id_car_make
|
||||
|
||||
if make.id_car_make not in inventory:
|
||||
inventory[make.id_car_make] = {
|
||||
'make_id': make.id_car_make,
|
||||
'make_name': make.get_local_name(),
|
||||
'total_cars': 0,
|
||||
'models': {}
|
||||
"make_id": make.id_car_make,
|
||||
"make_name": make.get_local_name(),
|
||||
"total_cars": 0,
|
||||
"models": {},
|
||||
}
|
||||
inventory[make.id_car_make]['total_cars'] += 1
|
||||
inventory[make.id_car_make]["total_cars"] += 1
|
||||
|
||||
# Model Level
|
||||
model = car.id_car_model
|
||||
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] = {
|
||||
'model_id': model.id_car_model,
|
||||
'model_name': model.get_local_name(),
|
||||
'total_cars': 0,
|
||||
'trims': {}
|
||||
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] = {
|
||||
"model_id": model.id_car_model,
|
||||
"model_name": model.get_local_name(),
|
||||
"total_cars": 0,
|
||||
"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 = car.id_car_trim
|
||||
if trim and trim.id_car_trim not in 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] = {
|
||||
'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
|
||||
if (
|
||||
trim
|
||||
and trim.id_car_trim
|
||||
not in 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
|
||||
] = {"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
|
||||
result = {
|
||||
'total_cars': cars.count(),
|
||||
'makes': [
|
||||
"total_cars": cars.count(),
|
||||
"makes": [
|
||||
{
|
||||
'make_id': make_data['make_id'],
|
||||
'make_name': make_data['make_name'],
|
||||
'total_cars': make_data['total_cars'],
|
||||
'models': [
|
||||
"make_id": make_data["make_id"],
|
||||
"make_name": make_data["make_name"],
|
||||
"total_cars": make_data["total_cars"],
|
||||
"models": [
|
||||
{
|
||||
'model_id': model_data['model_id'],
|
||||
'model_name': model_data['model_name'],
|
||||
'total_cars': model_data['total_cars'],
|
||||
'trims': list(model_data['trims'].values())
|
||||
"model_id": model_data["model_id"],
|
||||
"model_name": model_data["model_name"],
|
||||
"total_cars": model_data["total_cars"],
|
||||
"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()
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
return render(request, 'inventory/inventory_stats.html', {'inventory': result})
|
||||
return render(request, "inventory/inventory_stats.html", {"inventory": result})
|
||||
|
||||
|
||||
class CarDetailView(LoginRequiredMixin, DetailView):
|
||||
model = models.Car
|
||||
template_name = 'inventory/car_detail.html'
|
||||
context_object_name = 'car'
|
||||
template_name = "inventory/car_detail.html"
|
||||
context_object_name = "car"
|
||||
|
||||
|
||||
class CarFinanceCreateView(LoginRequiredMixin, CreateView):
|
||||
model = models.CarFinance
|
||||
form_class = forms.CarFinanceForm
|
||||
template_name = 'inventory/car_finance_form.html'
|
||||
template_name = "inventory/car_finance_form.html"
|
||||
|
||||
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)
|
||||
|
||||
def form_valid(self, form):
|
||||
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)
|
||||
|
||||
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):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['car'] = self.car
|
||||
context["car"] = self.car
|
||||
return context
|
||||
|
||||
|
||||
class CarFinanceUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = models.CarFinance
|
||||
form_class = forms.CarFinanceForm
|
||||
template_name = 'inventory/car_finance_form.html'
|
||||
template_name = "inventory/car_finance_form.html"
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
model = models.Car
|
||||
form_class = forms.CarUpdateForm
|
||||
template_name = 'inventory/car_edit.html'
|
||||
template_name = "inventory/car_edit.html"
|
||||
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('Car updated successfully.'))
|
||||
messages.success(self.request, _("Car updated successfully."))
|
||||
return super().form_valid(form)
|
||||
|
||||
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):
|
||||
model = models.Car
|
||||
template_name = 'inventory/car_confirm_delete.html'
|
||||
success_url = reverse_lazy('inventory_stats')
|
||||
template_name = "inventory/car_confirm_delete.html"
|
||||
success_url = reverse_lazy("inventory_stats")
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(request, _('Car deleted successfully.'))
|
||||
messages.success(request, _("Car deleted successfully."))
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
@ -421,26 +475,26 @@ class CustomCardCreateView(LoginRequiredMixin, CreateView):
|
||||
template_name = "inventory/add_custom_card.html"
|
||||
|
||||
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
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **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
|
||||
|
||||
def get_success_url(self):
|
||||
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):
|
||||
model = models.CarColors
|
||||
template_name = 'inventory/color_palette.html'
|
||||
template_name = "inventory/color_palette.html"
|
||||
|
||||
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()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@ -448,41 +502,45 @@ class CarColorCreateView(LoginRequiredMixin, CreateView):
|
||||
class ColorPickerForm(ModelForm):
|
||||
color = ChoiceField(
|
||||
choices=self.get_color_choices(),
|
||||
widget=RadioSelect(attrs={'class': 'color-picker'}),
|
||||
widget=RadioSelect(attrs={"class": "color-picker"}),
|
||||
label=_("Select a Color"),
|
||||
)
|
||||
color_type = ChoiceField(
|
||||
choices=models.CarColors.ColorType.choices,
|
||||
widget=RadioSelect(attrs={'class': 'color-type-picker'}),
|
||||
widget=RadioSelect(attrs={"class": "color-type-picker"}),
|
||||
label=_("Select Color Type"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = models.CarColors
|
||||
fields = ['color', 'color_type']
|
||||
fields = ["color", "color_type"]
|
||||
|
||||
return ColorPickerForm
|
||||
|
||||
def fetch_available_colors(self):
|
||||
car_data = {
|
||||
'make': self.car.id_car_make.name,
|
||||
'model': self.car.id_car_model.name,
|
||||
'year': str(self.car.year),
|
||||
"make": self.car.id_car_make.name,
|
||||
"model": self.car.id_car_model.name,
|
||||
"year": str(self.car.year),
|
||||
}
|
||||
return fetch_colors(car_data) or []
|
||||
|
||||
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):
|
||||
selected_rgb = form.cleaned_data['color']
|
||||
selected_rgb = form.cleaned_data["color"]
|
||||
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:
|
||||
messages.error(self.request, _('Invalid color selection.'))
|
||||
messages.error(self.request, _("Invalid color selection."))
|
||||
return self.form_invalid(form)
|
||||
|
||||
# Assign the car and selected color details
|
||||
@ -490,26 +548,26 @@ class CarColorCreateView(LoginRequiredMixin, CreateView):
|
||||
form.instance.rgb = selected_rgb
|
||||
form.instance.name = 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)
|
||||
|
||||
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):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['car'] = self.car
|
||||
context["car"] = self.car
|
||||
return context
|
||||
|
||||
|
||||
class CarColorUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = forms.CarColors
|
||||
template_name = 'inventory/color_palette.html'
|
||||
template_name = "inventory/color_palette.html"
|
||||
|
||||
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()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@ -517,50 +575,55 @@ class CarColorUpdateView(LoginRequiredMixin, UpdateView):
|
||||
class ColorPickerForm(ModelForm):
|
||||
color = ChoiceField(
|
||||
choices=self.get_color_choices(),
|
||||
widget=RadioSelect(attrs={'class': 'color-picker'}),
|
||||
widget=RadioSelect(attrs={"class": "color-picker"}),
|
||||
label=_("Select a Color"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = forms.CarColors
|
||||
fields = ['color']
|
||||
fields = ["color"]
|
||||
|
||||
return ColorPickerForm
|
||||
|
||||
def fetch_available_colors(self):
|
||||
car_data = {
|
||||
'make': self.car.id_car_make.name,
|
||||
'model': self.car.id_car_model.name,
|
||||
'year': str(self.car.year),
|
||||
"make": self.car.id_car_make.name,
|
||||
"model": self.car.id_car_model.name,
|
||||
"year": str(self.car.year),
|
||||
}
|
||||
return fetch_colors(car_data) or []
|
||||
|
||||
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):
|
||||
selected_rgb = form.cleaned_data['color']
|
||||
selected_rgb = form.cleaned_data["color"]
|
||||
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:
|
||||
messages.error(self.request, _('Invalid color selection.'))
|
||||
messages.error(self.request, _("Invalid color selection."))
|
||||
return self.form_invalid(form)
|
||||
|
||||
form.instance.rgb = selected_rgb
|
||||
form.instance.name = 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)
|
||||
|
||||
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):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['car'] = self.car
|
||||
context["car"] = self.car
|
||||
return context
|
||||
|
||||
|
||||
@ -570,26 +633,28 @@ def reserve_car_view(request, car_id):
|
||||
car = get_object_or_404(models.Car, pk=car_id)
|
||||
if car.is_reserved():
|
||||
messages.error(request, _("This car is already reserved."))
|
||||
return redirect('car_detail', pk=car.pk)
|
||||
return redirect("car_detail", pk=car.pk)
|
||||
|
||||
try:
|
||||
reserved_until = timezone.now() + timezone.timedelta(hours=24)
|
||||
models.CarReservation.objects.create(
|
||||
car=car,
|
||||
reserved_by=request.user,
|
||||
reserved_until=reserved_until
|
||||
car=car, reserved_by=request.user, reserved_until=reserved_until
|
||||
)
|
||||
messages.success(request, _("Car reserved successfully."))
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error reserving car: {e}")
|
||||
|
||||
return redirect('car_detail', pk=car.pk)
|
||||
return JsonResponse({"success": False, "message": "Invalid request method."}, status=400)
|
||||
return redirect("car_detail", pk=car.pk)
|
||||
return JsonResponse(
|
||||
{"success": False, "message": "Invalid request method."}, status=400
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
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":
|
||||
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.save()
|
||||
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":
|
||||
reservation.delete()
|
||||
messages.success(request, _("Reservation canceled successfully."))
|
||||
return redirect('car_detail', pk=reservation.car.pk)
|
||||
return redirect("car_detail", pk=reservation.car.pk)
|
||||
|
||||
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):
|
||||
model = models.Dealer
|
||||
template_name = 'dealer_list.html'
|
||||
context_object_name = 'dealers'
|
||||
template_name = "dealer_list.html"
|
||||
context_object_name = "dealers"
|
||||
|
||||
|
||||
class DealerDetailView(LoginRequiredMixin, DetailView):
|
||||
model = models.Dealer
|
||||
template_name = 'dealers/dealer_detail.html'
|
||||
context_object_name = 'dealer'
|
||||
template_name = "dealers/dealer_detail.html"
|
||||
context_object_name = "dealer"
|
||||
|
||||
|
||||
class DealerCreateView(LoginRequiredMixin, CreateView):
|
||||
model = models.Dealer
|
||||
form_class = forms.DealerForm
|
||||
template_name = 'dealer_form.html'
|
||||
success_url = reverse_lazy('dealer_list')
|
||||
template_name = "dealer_form.html"
|
||||
success_url = reverse_lazy("dealer_list")
|
||||
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('Dealer created successfully.'))
|
||||
messages.success(self.request, _("Dealer created successfully."))
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DealerUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = models.Dealer
|
||||
form_class = forms.DealerForm
|
||||
template_name = 'dealers/dealer_form.html'
|
||||
success_url = reverse_lazy('dealer_detail')
|
||||
template_name = "dealers/dealer_form.html"
|
||||
success_url = reverse_lazy("dealer_detail")
|
||||
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('Dealer updated successfully.'))
|
||||
messages.success(self.request, _("Dealer updated successfully."))
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DealerDeleteView(LoginRequiredMixin, DeleteView):
|
||||
model = models.Dealer
|
||||
template_name = 'dealer_confirm_delete.html'
|
||||
success_url = reverse_lazy('dealer_list')
|
||||
template_name = "dealer_confirm_delete.html"
|
||||
success_url = reverse_lazy("dealer_list")
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(request, _('Dealer deleted successfully.'))
|
||||
messages.success(request, _("Dealer deleted successfully."))
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
class CustomerListView(LoginRequiredMixin, ListView):
|
||||
model = models.Customer
|
||||
home_label = _('customers')
|
||||
context_object_name = 'customers'
|
||||
home_label = _("customers")
|
||||
context_object_name = "customers"
|
||||
paginate_by = 10
|
||||
template_name = "customers/customer_list.html"
|
||||
|
||||
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)
|
||||
|
||||
if query:
|
||||
customers = customers.filter(
|
||||
Q(national_id__icontains=query) |
|
||||
Q(first_name__icontains=query) |
|
||||
Q(last_name__icontains=query)
|
||||
Q(national_id__icontains=query)
|
||||
| Q(first_name__icontains=query)
|
||||
| Q(last_name__icontains=query)
|
||||
)
|
||||
return customers
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['query'] = self.request.GET.get('q', '')
|
||||
context["query"] = self.request.GET.get("q", "")
|
||||
return context
|
||||
|
||||
|
||||
class CustomerDetailView(LoginRequiredMixin, DetailView):
|
||||
model = models.Customer
|
||||
template_name = 'customers/view_customer.html'
|
||||
context_object_name = 'customer'
|
||||
template_name = "customers/view_customer.html"
|
||||
context_object_name = "customer"
|
||||
|
||||
|
||||
class CustomerCreateView(LoginRequiredMixin, CreateView):
|
||||
model = models.Customer
|
||||
form_class = forms.CustomerForm
|
||||
template_name = 'customers/customer_form.html'
|
||||
success_url = reverse_lazy('customer_list')
|
||||
template_name = "customers/customer_form.html"
|
||||
success_url = reverse_lazy("customer_list")
|
||||
|
||||
def form_valid(self, form):
|
||||
if form.is_valid():
|
||||
form.instance.dealer = self.request.user.dealer
|
||||
form.save()
|
||||
messages.success(self.request, _('Customer created successfully.'))
|
||||
messages.success(self.request, _("Customer created successfully."))
|
||||
return super().form_valid(form)
|
||||
else:
|
||||
return form.errors
|
||||
@ -704,14 +773,14 @@ class CustomerCreateView(LoginRequiredMixin, CreateView):
|
||||
class CustomerUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = models.Customer
|
||||
form_class = forms.CustomerForm
|
||||
template_name = 'customers/customer_form.html'
|
||||
success_url = reverse_lazy('customer_list')
|
||||
template_name = "customers/customer_form.html"
|
||||
success_url = reverse_lazy("customer_list")
|
||||
|
||||
def form_valid(self, form):
|
||||
if form.is_valid():
|
||||
form.instance.dealer = self.request.user.dealer
|
||||
form.save()
|
||||
messages.success(self.request, _('Customer updated successfully.'))
|
||||
messages.success(self.request, _("Customer updated successfully."))
|
||||
return super().form_valid(form)
|
||||
else:
|
||||
return form.errors
|
||||
@ -721,6 +790,7 @@ class CustomerUpdateView(LoginRequiredMixin, UpdateView):
|
||||
def delete_customer(request, pk):
|
||||
customer = get_object_or_404(models.Customer, pk=pk)
|
||||
customer.delete()
|
||||
<<<<<<< HEAD
|
||||
messages.success(request, _('Customer deleted successfully.'))
|
||||
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)
|
||||
|
||||
@ -16,7 +16,7 @@ dill==0.3.9
|
||||
distro==1.9.0
|
||||
dj-rest-auth==7.0.0
|
||||
dj-shop-cart==7.1.1
|
||||
Django==5.1.4
|
||||
Django
|
||||
django-allauth==65.3.0
|
||||
django-autoslug==1.9.9
|
||||
django-bootstrap5==24.3
|
||||
@ -74,7 +74,6 @@ platformdirs==4.3.6
|
||||
prometheus_client==0.21.1
|
||||
psycopg==3.2.3
|
||||
psycopg-binary==3.2.3
|
||||
psycopg-c==3.2.3
|
||||
py-moneyed==3.0
|
||||
pycodestyle==2.12.1
|
||||
pycparser==2.22
|
||||
|
||||
BIN
static/images/logos/users/MainAfter.jpg
Normal file
BIN
static/images/logos/users/MainAfter.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
@ -16,10 +16,15 @@
|
||||
{% endif %}
|
||||
<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 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://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/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>
|
||||
body {
|
||||
min-height: 100vh;
|
||||
@ -45,9 +50,11 @@
|
||||
<!-- JavaScript Files -->
|
||||
|
||||
<script type="text/javascript" src="{% static 'js/main.js' %}"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
$('.form-select').select2({
|
||||
theme: 'bootstrap4',
|
||||
});
|
||||
'use strict';
|
||||
|
||||
// Fetch all the forms with the "needs-validation" class
|
||||
|
||||
@ -436,6 +436,7 @@ async function decodeVin() {
|
||||
}
|
||||
|
||||
async function updateFields(vinData) {
|
||||
console.log(vinData)
|
||||
if (vinData.make_id) {
|
||||
makeSelect.value = vinData.make_id;
|
||||
await loadModels(vinData.make_id);
|
||||
@ -556,7 +557,7 @@ async function loadSpecifications(trimId){
|
||||
'X-CSRFToken': csrfToken
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
const data = await response.json();
|
||||
data.forEach((spec) => {
|
||||
const parentDiv = document.createElement('div');
|
||||
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
||||
@ -585,7 +586,7 @@ async function loadSpecifications(trimId){
|
||||
});
|
||||
|
||||
trimSelect.addEventListener('change', () => {
|
||||
const trimId = trimSelect.value;
|
||||
const trimId = trimSelect.value;
|
||||
showSpecificationButton.disabled = !trimId;
|
||||
if (trimId) loadSpecifications(trimId);
|
||||
});
|
||||
|
||||
@ -113,5 +113,4 @@
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
get_stats
|
||||
{% endblock %}
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load custom_filters %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user