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!
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,
}

View File

@ -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)

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,
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):

View File

@ -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',

View File

@ -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'),
]
]

View File

@ -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)

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -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

View File

@ -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);
});

View File

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

View File

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