diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..998f9ee1 --- /dev/null +++ b/.vscode/launch.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/car_inventory/settings.py b/car_inventory/settings.py index 4ab05d7e..87d03ebd 100644 --- a/car_inventory/settings.py +++ b/car_inventory/settings.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, } diff --git a/inventory/admin.py b/inventory/admin.py index 71b57989..a6f01e1e 100644 --- a/inventory/admin.py +++ b/inventory/admin.py @@ -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) diff --git a/inventory/management/commands/seed_customer.py b/inventory/management/commands/seed_customer.py new file mode 100644 index 00000000..4ef2518b --- /dev/null +++ b/inventory/management/commands/seed_customer.py @@ -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.')) \ No newline at end of file diff --git a/inventory/models.py b/inventory/models.py index 8b30724c..5469e2de 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -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): diff --git a/inventory/services.py b/inventory/services.py index 86835747..c476558a 100644 --- a/inventory/services.py +++ b/inventory/services.py @@ -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', diff --git a/inventory/urls.py b/inventory/urls.py index 28a3f3dd..ccde353e 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -62,7 +62,4 @@ urlpatterns = [ path('cars/reserve//', views.reserve_car_view, name='reserve_car'), path('reservations//', views.manage_reservation, name='reservations'), path('cars//add-custom-card/', views.CustomCardCreateView.as_view(), name='add_custom_card'), - -] - - +] \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index 016c3bb0..faa94f9b 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -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) diff --git a/requirements.txt b/requirements.txt index 098b8973..878ed7a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/static/images/logos/users/MainAfter.jpg b/static/images/logos/users/MainAfter.jpg new file mode 100644 index 00000000..632e369f Binary files /dev/null and b/static/images/logos/users/MainAfter.jpg differ diff --git a/templates/base.html b/templates/base.html index 9ee01ed4..d6b62c0a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -16,10 +16,15 @@ {% endif %} + + + - + + +