diff --git a/car_inventory/__pycache__/settings.cpython-311.pyc b/car_inventory/__pycache__/settings.cpython-311.pyc index 76807e67..83e468dc 100644 Binary files a/car_inventory/__pycache__/settings.cpython-311.pyc and b/car_inventory/__pycache__/settings.cpython-311.pyc differ diff --git a/inventory/__pycache__/admin.cpython-311.pyc b/inventory/__pycache__/admin.cpython-311.pyc index c454076d..9025ad27 100644 Binary files a/inventory/__pycache__/admin.cpython-311.pyc and b/inventory/__pycache__/admin.cpython-311.pyc differ diff --git a/inventory/__pycache__/models.cpython-311.pyc b/inventory/__pycache__/models.cpython-311.pyc index 7ca358df..cc0cdbbd 100644 Binary files a/inventory/__pycache__/models.cpython-311.pyc and b/inventory/__pycache__/models.cpython-311.pyc differ diff --git a/inventory/__pycache__/tables.cpython-311.pyc b/inventory/__pycache__/tables.cpython-311.pyc index a5eed614..c99760b1 100644 Binary files a/inventory/__pycache__/tables.cpython-311.pyc and b/inventory/__pycache__/tables.cpython-311.pyc differ diff --git a/inventory/__pycache__/urls.cpython-311.pyc b/inventory/__pycache__/urls.cpython-311.pyc index ffa9b697..ef552762 100644 Binary files a/inventory/__pycache__/urls.cpython-311.pyc and b/inventory/__pycache__/urls.cpython-311.pyc differ diff --git a/inventory/__pycache__/utils.cpython-311.pyc b/inventory/__pycache__/utils.cpython-311.pyc index 1c8d967d..8a743419 100644 Binary files a/inventory/__pycache__/utils.cpython-311.pyc and b/inventory/__pycache__/utils.cpython-311.pyc differ diff --git a/inventory/__pycache__/views.cpython-311.pyc b/inventory/__pycache__/views.cpython-311.pyc index d8666eb9..30b9dfa2 100644 Binary files a/inventory/__pycache__/views.cpython-311.pyc and b/inventory/__pycache__/views.cpython-311.pyc differ diff --git a/inventory/admin.py b/inventory/admin.py index 031cd50a..d19e9480 100644 --- a/inventory/admin.py +++ b/inventory/admin.py @@ -4,7 +4,18 @@ from . import models from django_ledger import models as ledger_models from django_pdf_actions.actions import export_to_pdf_landscape, export_to_pdf_portrait from appointment import models as appointment_models +from import_export.admin import ExportMixin +from import_export.resources import ModelResource +# Define resource class +# class CarSerieResource(ModelResource): +# class Meta: +# model = models.CarSerie +# +# # Integrate into Django Admin +# @admin.register(models.CarSerie) +# class CarSeriesAdmin(ExportMixin, admin.ModelAdmin): +# resource_class = CarSerieResource admin.site.register(models.Dealer) admin.site.register(models.Staff) @@ -29,7 +40,7 @@ admin.site.register(models.CarLocation) admin.site.register(models.CarReservation) admin.site.register(models.Organization) admin.site.register(models.Representative) -admin.site.register(models.CarTrim) +# admin.site.register(models.CarTrim) admin.site.register(models.AdditionalServices) admin.site.register(models.Payment) admin.site.register(models.VatRate) @@ -82,7 +93,7 @@ class CarModelAdmin(admin.ModelAdmin): @admin.register(models.CarSerie) class CarSeriesAdmin(admin.ModelAdmin): list_display = ('name', 'arabic_name', 'id_car_model', ) - search_fields = ('id_car_serie', 'name', 'id_car_model__name', 'id_car_model__id_car_make__name') + search_fields = ('name',) list_filter = ('id_car_model__id_car_make__is_sa_import', 'id_car_model__id_car_make__name',) @@ -90,18 +101,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/serie_translate.py b/inventory/management/commands/serie_translate.py new file mode 100644 index 00000000..8cb887aa --- /dev/null +++ b/inventory/management/commands/serie_translate.py @@ -0,0 +1,69 @@ +from django.core.management.base import BaseCommand +from inventory.models import CarSerie + +TRANSLATIONS = { + "Sedan": "سيدان", + "Coupe": "كوبيه", + "SUV 5 doors": "إس يو في - خمسة أبواب", + "Minivan": "ميني فان", + "Hatchback": "هاتشباك", + "Cabriolet": "سطح قابل للطي", + "Hatchback 5-doors": "هاتشباك - خمسة أبواب", + "Hatchback 5 doors": "هاتشباك - خمسة أبواب", + "Hatchback 3-doors": "هاتشباك - ثلاثة أبواب", + "Crossover": "كروس أوفر", + "Wagon": "واغن", + "SUV": "إس يو في", + "Wagon 5 doors": "واغن - خمسة أبواب", + "Roadster": "رودستر", + "SUV 5-doors": "إس يو في - خمسة أبواب", + "Wagon 5-doors": "واغن - خمسة أبواب", + "Sedan 4-doors": "سيدان - أربعة أبواب", + "Hatchback 3 doors": "هاتشباك - ثلاثة أبواب", + "Van": "فان", + "Pickup Double cabin": "بيك أب - غمارتين", + "Compactvan": "كومباكت فان", + "Pickup": "بيك أب", + "Microvan": "ميكروفان", + "Liftback": "ليفت باك", + "SUV 3-doors": "إس يو في - ثلاثة أبواب", + "Coupe 2-doors": "كوبيه - بابين", + "Pickup Single cabin": "بيك أب - غمارة واحدة", + "Crossover 5-doors": "كروس أوفر - خمسة أبواب", + "Coupe-Hardtop": "كوبيه هاردتوب", + "SUV 3 doors": "إس يو في - ثلاثة أبواب", + "Hardtop": "هاردتوب", + "Sedan 2-doors": "سيدان - بابين", + "Minivan 5-doors": "ميني فان - خمسة أبواب", + "Targa": "تارغا", + "SUV opened": "إس يو في مكشوف", + "Pickup One-and-a-half cabin": "بيك أب - غمارة ونصف", + "Sedan 2 doors": "سيدان - بابين", + "AMG Sedan 4-doors": "أي إم جي سيدان - أربعة أبواب", + "Cabriolet 2-doors": "سطح قابل للطي - بابين", + "Fastback": "فاست باك", + "Sedan-Hardtop": "سيدان هاردتوب", + "Regular Cab pickup 2-doors": "بيك أب كابينة عادية - بابين", + "Grand minivan 5-doors": "ميني فان كبير - خمسة أبواب", + "Sedan Long": "سيدان طويل", + "Speedster": "سبيدستر", +} + + +class Command(BaseCommand): + help = "Translate CarSerie model names into Arabic" + + def handle(self, *args, **kwargs): + updated_count = 0 + for car_serie in CarSerie.objects.all(): + arabic_translation = TRANSLATIONS.get(car_serie.name) + if arabic_translation and car_serie.arabic_name != arabic_translation: + car_serie.arabic_name = arabic_translation + car_serie.save() + updated_count += 1 + self.stdout.write(self.style.SUCCESS(f"Updated: {car_serie.name} -> {arabic_translation}")) + + if updated_count: + self.stdout.write(self.style.SUCCESS(f"Successfully updated {updated_count} entries.")) + else: + self.stdout.write(self.style.WARNING("No updates were made.")) diff --git a/inventory/models.py b/inventory/models.py index e7780b34..cb899255 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -416,7 +416,7 @@ class Car(models.Model): @property def ready(self): try: - return all([self.colors.exists() ,self.finances,]) + return all([self.colors ,self.finances,]) except Exception as e: return False def get_transfer(self): diff --git a/inventory/tables.py b/inventory/tables.py index e0fcf33a..01288b6d 100644 --- a/inventory/tables.py +++ b/inventory/tables.py @@ -1,10 +1,15 @@ from django.utils.html import format_html from django.conf import settings +from django.utils.timesince import timesince + from . import models from django_tables2.utils import A -import django_tables2 as tables from django import forms +import django_tables2 as tables +from django.utils.translation import gettext_lazy as _ +from .models import Car, CarFinance, ExteriorColors, InteriorColors, CarColors +from .utils import get_local_name @@ -13,88 +18,50 @@ class ImageColumn(tables.Column): return format_html('', settings.MEDIA_URL, value) -class CustomerTable(tables.Table): - class Meta: - model = models.Customer - first_name = tables.Column() - - -import django_tables2 as tables -from django.utils.translation import gettext_lazy as _ -from .models import Car, CarFinance, ExteriorColors, InteriorColors, CarColors - class CarTable(tables.Table): - # Car fields - vin = tables.Column(verbose_name=_("VIN")) - dealer = tables.Column(verbose_name=_("Dealer")) - vendor = tables.Column(verbose_name=_("Vendor")) + stock_type = tables.Column(verbose_name=_("Stock Type")) + vin = tables.LinkColumn("car_detail", args=[tables.A("pk")], verbose_name=_("VIN"), attrs={"td": {"class": "fw-bold"}, "span": {"class": "fas fa-bars"}}) id_car_make = tables.Column(verbose_name=_("Make")) id_car_model = tables.Column(verbose_name=_("Model")) year = tables.Column(verbose_name=_("Year")) id_car_serie = tables.Column(verbose_name=_("Series")) id_car_trim = tables.Column(verbose_name=_("Trim")) - status = tables.Column(verbose_name=_("Status")) - stock_type = tables.Column(verbose_name=_("Stock Type")) - remarks = tables.Column(verbose_name=_("Remarks")) mileage = tables.Column(verbose_name=_("Mileage")) - receiving_date = tables.Column(verbose_name=_("Receiving Date")) - hash = tables.Column(verbose_name=_("Hash")) - - # CarFinance fields - cost_price = tables.Column(accessor="finances.cost_price", verbose_name=_("Cost Price")) - selling_price = tables.Column(accessor="finances.selling_price", verbose_name=_("Selling Price")) - discount_amount = tables.Column(accessor="finances.discount_amount", verbose_name=_("Discount Amount")) - - # ExteriorColors fields (accessed through CarColors) + selling_price = tables.Column(accessor="finances.selling_price", verbose_name=_("Price")) exterior_color = tables.Column(accessor="colors.exterior.name", verbose_name=_("Exterior Color")) - exterior_color_rgb = tables.Column(accessor="colors.exterior.rgb", verbose_name=_("Exterior Color RGB")) - - # InteriorColors fields (accessed through CarColors) interior_color = tables.Column(accessor="colors.interior.name", verbose_name=_("Interior Color")) - interior_color_rgb = tables.Column(accessor="colors.interior.rgb", verbose_name=_("Interior Color RGB")) + receiving_date = tables.Column(verbose_name=_("Age")) + status = tables.Column(verbose_name=_("Status")) class Meta: model = Car - template_name = "django_tables2/bootstrap.html" + template_name = settings.DJANGO_TABLES2_TEMPLATE + export_formats = ["xlsx", ] fields = ( + "stock_type", "vin", - "dealer", - "vendor", "id_car_make", "id_car_model", "year", "id_car_serie", "id_car_trim", - "status", - "stock_type", - "remarks", "mileage", - "receiving_date", - "hash", - "cost_price", "selling_price", - "discount_amount", "exterior_color", - "exterior_color_rgb", "interior_color", - "interior_color_rgb", + "receiving_date", + "status", ) - attrs = {"class": "table table-striped table-bordered"} - - def render_dealer(self, value): - return str(value) - - def render_vendor(self, value): - return str(value) + attrs = {"class": "table table-hover fs-9 mb-0"} def render_id_car_make(self, value): - return str(value) + return get_local_name(value) def render_id_car_model(self, value): - return str(value) + return get_local_name(value) def render_id_car_serie(self, value): - return str(value) + return get_local_name(value) def render_id_car_trim(self, value): return str(value) @@ -103,4 +70,7 @@ class CarTable(tables.Table): return str(value) def render_interior_color(self, value): - return str(value) \ No newline at end of file + return str(value) + + def render_receiving_date(self, value): + return timesince(value) if value else "-" \ No newline at end of file diff --git a/inventory/urls.py b/inventory/urls.py index 49abe150..25b5f6fa 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -1,7 +1,9 @@ from django.urls import path +from django_tables2.export.export import TableExport + from . import views from allauth.account import views as allauth_views -from django_tables2.export.views import TableExport + urlpatterns = [ @@ -46,8 +48,8 @@ urlpatterns = [ "dashboards/accounting/", views.AccountingDashboard.as_view(), name="accounting" ), path("test/", views.TestView.as_view(), name="test"), - path('cars/inventory/table/', views.car_list, name="car_table"), - path('export//', TableExport, name='export'), + path('cars/inventory/table/', views.CarListViewTable.as_view(), name="car_table"), + path("export/format/", TableExport, name="export"), # Dealer URLs path("dealers//", views.DealerDetailView.as_view(), name="dealer_detail"), path( diff --git a/inventory/utils.py b/inventory/utils.py index 7ca94675..b95de92b 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -25,6 +25,7 @@ from django_ledger.models import ( ItemTransactionModel ) from decimal import Decimal +from django.utils.translation import get_language @@ -838,4 +839,13 @@ def get_item_transactions(txs): if bool(data): transactions.append(data) print(data) - return transactions \ No newline at end of file + return transactions + + +def get_local_name(self): + """ + Returns the localized name based on the current language. + """ + if get_language() == 'ar': + return getattr(self, 'arabic_name', None) + return getattr(self, 'name', None) \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index f86dba16..18ec36f6 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -3,7 +3,8 @@ from appointment.models import Appointment,AppointmentRequest,Service,StaffMembe from datetime import timedelta from calendar import month_name from random import randint -from django_tables2 import RequestConfig +from django_tables2 import SingleTableView +from django_tables2.export.views import ExportMixin from django_pdf_actions.actions import export_to_pdf_landscape from reportlab.lib.pagesizes import landscape, A4 from rich import print @@ -4387,11 +4388,14 @@ def apply_search_filters(queryset, query): return queryset.filter(search_filters).distinct() -def car_list(request): - dealer = get_user_type(request) - queryset = models.Car.objects.select_related( - "finances", "colors__exterior", "colors__interior" - ).filter(dealer=dealer) - table = tables.CarTable(queryset) - RequestConfig(request).configure(table) - return render(request, 'inventory/car_list_table.html', {'table': table}) \ No newline at end of file +class CarListViewTable(ExportMixin, LoginRequiredMixin, SingleTableView): + model = models.Car + table_class = tables.CarTable + template_name = "inventory/car_list_table.html" + + def get_queryset(self): + dealer = get_user_type(self.request) + return models.Car.objects.select_related( + "finances", "colors__exterior", "colors__interior" + ).filter(dealer=dealer) + diff --git a/requirements.txt b/requirements.txt index ad22e107..95b500d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,6 +35,7 @@ Cython==3.1.0a1 decorator==5.1.1 defusedxml==0.7.1 desert==2020.11.18 +diff-match-patch==20241021 dill==0.3.9 distro==1.9.0 dj-rest-auth==7.0.1 @@ -52,6 +53,7 @@ django-debug-toolbar==5.0.1 django-extensions==3.2.3 django-filter==25.1 django-formtools==2.5.1 +django-import-export==4.3.5 django-ledger==0.7.4.1 django-model-utils==5.0.0 django-money==3.5.3 @@ -133,7 +135,7 @@ networkx==3.4.2 newrelic==10.6.0 nltk==3.9.1 num2words==0.5.14 -libquadmath==2.2.3 +numpy==2.2.3 oauthlib==3.2.2 ofxtools==0.9.5 openai==1.63.1 @@ -176,6 +178,7 @@ pyobjc-framework-Cocoa==11.0 pyobjc-framework-Quartz==11.0 pyparsing==3.2.1 pypdf==5.3.0 +PyPDF2==3.0.1 pyperclip==1.9.0 pyphen==0.17.2 pypng==0.20220715.0 @@ -208,8 +211,8 @@ rich==13.9.4 rubicon-objc==0.5.0 sacremoses==0.1.1 scikit-image==0.25.1 -libomp runtime library==1.6.1 -libquadmath==1.15.2 +scikit-learn==1.6.1 +scipy==1.15.2 selenium==4.28.1 sentencepiece==0.2.0 shapely==2.0.7 diff --git a/static/images/icons/excel.png b/static/images/icons/excel.png new file mode 100644 index 00000000..48fab56d Binary files /dev/null and b/static/images/icons/excel.png differ diff --git a/static/images/icons/pdf.png b/static/images/icons/pdf.png new file mode 100644 index 00000000..856080f6 Binary files /dev/null and b/static/images/icons/pdf.png differ diff --git a/templates/inventory/car_detail.html b/templates/inventory/car_detail.html index 8e089c74..28720d3e 100644 --- a/templates/inventory/car_detail.html +++ b/templates/inventory/car_detail.html @@ -244,27 +244,27 @@
- {% if car.colors.exists %} - {% for color in car.colors.all %} + {% if car.colors %} + - {% endfor %} + {% else %} - {% if car.colors.exists %} + {% if car.colors %} {% else %} diff --git a/templates/inventory/car_list_table.html b/templates/inventory/car_list_table.html index 6fd3d636..b5d3ae91 100644 --- a/templates/inventory/car_list_table.html +++ b/templates/inventory/car_list_table.html @@ -1,15 +1,26 @@ {% extends 'base.html' %} -{% load static i18n django_tables2%} +{% load static i18n django_tables2 %} +{% load export_url from django_tables2 %} {% block content %} +
+
+
+ + + +
+
+
+
+
+ {% render_table table %} +
+
+
+
-{% load export_url from django_tables2 %} - -{% render_table table %} - -Export to Excel -Export to PDF {% endblock %} \ No newline at end of file diff --git a/templates/partials/pagination.html b/templates/partials/pagination.html index 669eb5e4..67cebdf1 100644 --- a/templates/partials/pagination.html +++ b/templates/partials/pagination.html @@ -4,11 +4,11 @@
{% if page_obj.has_previous %} {% else %} {% endif %} @@ -28,11 +28,11 @@ {% if page_obj.has_next %} {% else %} {% endif %}
{% trans 'Exterior' %} - {{ color.exterior.get_local_name }} + {{ car.colors.exterior.get_local_name }} -
+
{% trans 'Interior' %} - {{ color.interior.get_local_name }} + {{ car.colors.interior.get_local_name }} -
+
diff --git a/templates/inventory/car_inventory.html b/templates/inventory/car_inventory.html index a8bdd2fa..babf7979 100644 --- a/templates/inventory/car_inventory.html +++ b/templates/inventory/car_inventory.html @@ -72,15 +72,15 @@ {{ car.vin }} {{ car.year }}
- {{ car.colors.first.exterior.get_local_name }} + {{ car.colors.exterior.get_local_name }}
- {{ car.colors.first.interior.get_local_name }} + {{ car.colors.interior.get_local_name }}