first commit

This commit is contained in:
Marwan Alwali 2024-12-08 14:07:50 +03:00
commit 997b3cef10
353 changed files with 1583107 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
.idea/.DS_Store generated vendored Normal file

Binary file not shown.

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

7
.idea/JetClient/state.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JetClientState">
<option name="id" value="9643c603-9e26-490a-916b-222ca66b0b6e" />
<option name="version" value="3" />
</component>
</project>

37
.idea/car_inventory.iml generated Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="car_inventory/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="jquery-3.5.1" level="application" />
<orderEntry type="library" name="sweetalert2" level="application" />
<orderEntry type="library" name="jquery" level="application" />
<orderEntry type="library" name="bootstrap-icons" level="application" />
<orderEntry type="library" name="quagga" level="application" />
<orderEntry type="library" name="@zxing" level="application" />
<orderEntry type="library" name="tesseract.js" level="application" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
</module>

View File

@ -0,0 +1,17 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="Django" />
<item index="1" class="java.lang.String" itemvalue="django-admin-volt" />
<item index="2" class="java.lang.String" itemvalue="faker_food" />
<item index="3" class="java.lang.String" itemvalue="typing_extensions" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{@zxing, bootstrap-icons, jquery, jquery-3.5.1, quagga, sweetalert2, tesseract.js}" />
</component>
</project>

12
.idea/material_theme_project_new.xml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="-4d33890d:18fe9fd09b1:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>

7
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11 (car_inventory)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (car_inventory)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/car_inventory.iml" filepath="$PROJECT_DIR$/.idea/car_inventory.iml" />
</modules>
</component>
</project>

0
api/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

5
api/admin.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib import admin
from . import models
admin.site.register(models.CarVIN)

6
api/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'

35
api/consumers.py Normal file
View File

@ -0,0 +1,35 @@
# from channels.generic.websocket import AsyncWebsocketConsumer
# from api.services import get_car_data
# import json
#
#
# class VINScanConsumer(AsyncWebsocketConsumer):
# def __init__(self, *args, **kwargs):
# super().__init__(args, kwargs)
# self.group_name = None
#
# async def connect(self):
# self.group_name = 'vin_scan_group'
# await self.channel_layer.group_add(
# self.group_name,
# self.channel_name
# )
# await self.accept()
#
# async def disconnect(self, close_code):
# await self.channel_layer.group_discard(
# self.group_name,
# self.channel_name
# )
#
# async def receive(self, text_data, **kwargs):
# data = json.loads(text_data)
# vin = data['vin']
# get_car_data(vin)
# # Process the VIN as needed, e.g., save to the database
#
# # Send data back to the WebSocket
# await self.send(text_data=json.dumps({
# 'message': 'VIN received',
# 'vin': vin
# }))

View File

@ -0,0 +1,22 @@
# Generated by Django 5.0.6 on 2024-08-21 11:31
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='CarVIN',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vin', models.CharField(max_length=17, verbose_name='VIN')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
],
),
]

View File

Binary file not shown.

11
api/models.py Normal file
View File

@ -0,0 +1,11 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class CarVIN(models.Model):
vin = models.CharField(max_length=17, verbose_name=_("VIN"))
created = models.DateTimeField(auto_now_add=True, verbose_name="created")
def __str__(self):
return self.vin

6
api/routing.py Normal file
View File

@ -0,0 +1,6 @@
# from django.urls import re_path
# from api import consumers
#
# websocket_urlpatterns = [
# re_path(r'ws/vin_scan/$', consumers.VINScanConsumer.as_asgi()),
# ]

14
api/serializers.py Normal file
View File

@ -0,0 +1,14 @@
from rest_framework import serializers
from . import models
class CarVINSerializer(serializers.ModelSerializer):
class Meta:
model = models.CarVIN
fields = ['vin']
def create(self, validated_data):
vin = validated_data.pop('vin')
return models.CarVIN.objects.create(vin=vin, **validated_data)

63
api/services.py Normal file
View File

@ -0,0 +1,63 @@
import hashlib
import json
import requests
def get_bearer():
api_token = "f5204a00-6f31-4de2-96d8-ed998e0d230c"
api_secret = "8c11320781a5b8f4f327b6937e6f8241"
url = "https://carapi.app/api/auth/login"
headers = {
"accept": "text/plain",
"Content-Type": "application/json"
}
data = {
"api_token": api_token,
"api_secret": api_secret
}
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
return response.text
else:
print("the api key or secret is not valid")
return None
def get_car_data(vin):
url = f"https://carapi.app/api/vin/{vin}?verbose=no&all_trims=no"
headers = {
"Authorization": f"Bearer {get_bearer()}"
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status() # Raises an HTTPError for bad responses (4XX, 5XX)
response = response.json()
print(response)
except requests.exceptions.HTTPError as http_err:
print(f"HTTP error occurred: {http_err}")
except requests.exceptions.RequestException as err:
print(f"Error occurred: {err}")
except Exception as e:
print(f"An error occurred: {e}")
def get_from_cardatabase(vin):
vin = vin
url = "https://api.vehicledatabases.com/premium/vin-decode/{vin}"
payload = {}
headers = {
'x-AuthKey': '3cefdfd4272445f1929b5801c55d8fa5'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)

797
api/temp.txt Normal file
View File

@ -0,0 +1,797 @@
# url = f"https://carapi.app/api/vin/{vin}?verbose=no&all_trims=no"
# headers = {
# "Authorization": f"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjYXJhcGkuYXBwIiwic3ViIjoiYjU1OGYzMDMtODI0Ni00NjgzLTkwYTQtZmYwMGQxYWNmNGU3IiwiYXVkIjoiYjU1OGYzMDMtODI0Ni00NjgzLTkwYTQtZmYwMGQxYWNmNGU3IiwiZXhwIjoxNzI0MzgyODMzLCJpYXQiOjE3MjM3NzgwMzMsImp0aSI6IjA3ZDUzM2Y5LWRmNzEtNDM1My1hMTBhLTNiYmQ2MDljNWM0YiIsInVzZXIiOnsic3Vic2NyaWJlZCI6dHJ1ZSwic3Vic2NyaXB0aW9uIjoic3RhcnRlciIsInJhdGVfbGltaXRfdHlwZSI6ImhhcmQiLCJhZGRvbnMiOnsiYW50aXF1ZV92ZWhpY2xlcyI6ZmFsc2UsImRhdGFfZmVlZCI6ZmFsc2V9fX0.KnaH-dOGcmJAF3WqzrZ_c_eFtkSjPVEjR15G7KFxi2s"
# }
#
# try:
# response = requests.get(url, headers=headers)
# response.raise_for_status() # Raises an HTTPError for bad responses (4XX, 5XX)
#
# response = response.json()
# print(response)
#
# except requests.exceptions.HTTPError as http_err:
# print(f"HTTP error occurred: {http_err}")
# except requests.exceptions.RequestException as err:
# print(f"Error occurred: {err}")
# except Exception as e:
# print(f"An error occurred: {e}")
# class TokenAuthenticationMiddleware(MiddlewareMixin):
#
# def process_request(self, request, *args, **kwargs):
#
# exempt_paths = ['/login/', '/signup/', '/']
# if request.path in exempt_paths:
# return None
#
# token_key = request.META.get('HTTP_AUTHORIZATION')
# if token_key:
# token_key = token_key.split(' ')[1] if ' ' in token_key else token_key
# try:
# token = Token.objects.get(key=token_key)
# request.user = token.user
# except Token.DoesNotExist:
# return HttpResponseForbidden("Invalid token")
#
# if not request.user.is_authenticated:
# return HttpResponseForbidden("You are not authorized to view this page")
#
# return None
# def vin_decode(vin):
# apiKey = "67b751455994"
# secretKey = "2ca9c09960"
# apiPrefix = "https://api.vindecoder.eu/3.2"
# id = "decode"
#
# controlSum = hashlib.sha1((vin.upper() + "|" + id + "|" + apiKey + "|" + secretKey).encode('utf-8')).hexdigest()[
# :10]
# url = apiPrefix + "/" + apiKey + "/" + controlSum + "/decode/" + vin.upper() + ".json"
# print(url)
# json_data = requests.get(url).json()
# print(json_data)
# return json_data
{
"year": 2007,
"make": "CHEVROLET",
"model": "TrailBlazer",
"trim": "1/2 Ton",
"specs": {
"active_safety_system_note": null,
"adaptive_cruise_control_acc": null,
"adaptive_driving_beam_adb": null,
"anti_lock_braking_system_abs": null,
"auto_reverse_system_for_windows_and_sunroofs": null,
"automatic_crash_notification_acn_advanced_automatic_crash_notification_aacn": null,
"automatic_pedestrian_alerting_sound_for_hybrid_and_ev_only": null,
"axle_configuration": null,
"axles": null,
"backup_camera": null,
"base_price": null,
"battery_current_amps_from": null,
"battery_current_amps_to": null,
"battery_energy_kwh_from": null,
"battery_energy_kwh_to": null,
"battery_type": null,
"battery_voltage_volts_from": null,
"battery_voltage_volts_to": null,
"bed_length_inches": null,
"bed_type": null,
"blind_spot_intervention_bsi": null,
"blind_spot_warning_bsw": null,
"body_class": "Sport Utility Vehicle (SUV)/Multi-Purpose Vehicle (MPV)",
"brake_system_description": null,
"brake_system_type": "Hydraulic",
"bus_floor_configuration_type": "Not Applicable",
"bus_length_feet": null,
"bus_type": "Not Applicable",
"cab_type": null,
"charger_level": null,
"charger_power_kw": null,
"cooling_type": "Water",
"crash_imminent_braking_cib": null,
"curb_weight_pounds": null,
"curtain_air_bag_locations": null,
"custom_motorcycle_type": "Not Applicable",
"daytime_running_light_drl": null,
"destination_market": null,
"displacement_cc": "4200.0",
"displacement_ci": "256.29972519787",
"displacement_l": "4.2",
"doors": "4",
"drive_type": "4x2",
"dynamic_brake_support_dbs": null,
"electrification_level": null,
"electronic_stability_control_esc": null,
"engine_brake_hp_from": null,
"engine_brake_hp_to": null,
"engine_configuration": "In-Line",
"engine_manufacturer": "GM",
"engine_model": "LL8",
"engine_number_of_cylinders": "6",
"engine_power_kw": null,
"engine_stroke_cycles": null,
"entertainment_system": null,
"ev_drive_unit": null,
"event_data_recorder_edr": null,
"forward_collision_warning_fcw": null,
"front_air_bag_locations": null,
"fuel_delivery_fuel_injection_type": "Multipoint Fuel Injection (MPFI)",
"fuel_type_primary": "Gasoline",
"fuel_type_secondary": null,
"gross_combination_weight_rating_from": null,
"gross_combination_weight_rating_to": null,
"gross_vehicle_weight_rating_from": "Class 1D: 5,001 - 6,000 lb (2,268 - 2,722 kg)",
"gross_vehicle_weight_rating_to": null,
"headlamp_light_source": null,
"keyless_ignition": null,
"knee_air_bag_locations": null,
"lane_centering_assistance": null,
"lane_departure_warning_ldw": null,
"lane_keeping_assistance_lka": null,
"manufacturer_name": "GENERAL MOTORS LLC",
"motorcycle_chassis_type": "Not Applicable",
"motorcycle_suspension_type": "Not Applicable",
"ncsa_body_type": "Compact Utility (Utility Vehicle Categories \"Small\" and \"Midsize\")",
"ncsa_make": "Chevrolet",
"ncsa_model": "TrailBlazer (2003 on; for 2002 model, see 401)",
"ncsa_note": null,
"non_land_use": null,
"note": null,
"number_of_battery_cells_per_module": null,
"number_of_battery_modules_per_pack": null,
"number_of_battery_packs_per_vehicle": null,
"number_of_seat_rows": null,
"number_of_seats": null,
"number_of_wheels": null,
"other_battery_info": null,
"other_bus_info": null,
"other_engine_info": "MFI, DOHC, Aluminium",
"other_motorcycle_info": null,
"other_restraint_system_info": null,
"other_trailer_info": null,
"parking_assist": null,
"pedestrian_automatic_emergency_braking_paeb": null,
"plant_city": "MORAINE",
"plant_company_name": "GM Truck Group",
"plant_country": "UNITED STATES (USA)",
"plant_state": "OHIO",
"possible_values": "",
"pretensioner": null,
"rear_automatic_emergency_braking": null,
"rear_cross_traffic_alert": null,
"sae_automation_level_from": null,
"sae_automation_level_to": null,
"seat_belt_type": null,
"seat_cushion_air_bag_locations": null,
"semiautomatic_headlamp_beam_switching": null,
"series": "1/2 Ton",
"series2": null,
"side_air_bag_locations": null,
"steering_location": null,
"suggested_vin": "",
"tire_pressure_monitoring_system_tpms_type": null,
"top_speed_mph": null,
"track_width_inches": null,
"traction_control": null,
"trailer_body_type": "Not Applicable",
"trailer_length_feet": null,
"trailer_type_connection": "Not Applicable",
"transmission_speeds": null,
"transmission_style": null,
"trim": null,
"trim2": null,
"turbo": null,
"valve_train_design": "Dual Overhead Cam (DOHC)",
"vehicle_descriptor": "1GNDS13S*72",
"vehicle_type": "MULTIPURPOSE PASSENGER VEHICLE (MPV)",
"wheel_base_inches_from": null,
"wheel_base_inches_to": null,
"wheel_base_type": null,
"wheel_size_front_inches": null,
"wheel_size_rear_inches": null,
"windows": null
},
"trims": [
{
"id": 40805,
"make_model_id": 33,
"year": 2007,
"name": "LS",
"description": "LS 4dr SUV (4.2L 6cyl 4A)",
"msrp": 25045,
"invoice": 23417,
"created": "2023-06-29T21:13:16-04:00",
"modified": "2023-06-29T21:13:16-04:00",
"make_model_trim_mileage": {
"id": 40805,
"make_model_trim_id": 40805,
"fuel_tank_capacity": "22.0",
"combined_mpg": 16,
"epa_city_mpg": 14,
"epa_highway_mpg": 20,
"range_city": 308,
"range_highway": 440,
"battery_capacity_electric": null,
"epa_time_to_charge_hr_240v_electric": null,
"epa_kwh_100_mi_electric": null,
"range_electric": null,
"epa_highway_mpg_electric": null,
"epa_city_mpg_electric": null,
"epa_combined_mpg_electric": null
},
"make_model_trim_engine": {
"id": 40805,
"make_model_trim_id": 40805,
"engine_type": "gas",
"fuel_type": "regular unleaded",
"cylinders": "I6",
"size": "4.2",
"horsepower_hp": 291,
"horsepower_rpm": 6000,
"torque_ft_lbs": 277,
"torque_rpm": 4800,
"valves": 24,
"valve_timing": "Variable",
"cam_type": "Double overhead cam (DOHC)",
"drive_type": "rear wheel drive",
"transmission": "4-speed automatic"
},
"make_model_trim_body": {
"id": 40805,
"make_model_trim_id": 40805,
"type": "SUV",
"doors": 5,
"length": "191.8",
"width": "74.7",
"seats": 5,
"height": "72.5",
"wheel_base": "113.0",
"front_track": "62.7",
"rear_track": "62.0",
"ground_clearance": "7.8",
"cargo_capacity": "43.7",
"max_cargo_capacity": "80.1",
"curb_weight": 4356,
"gross_weight": 5550,
"max_payload": 1194,
"max_towing_capacity": 6800
},
"make_model": {
"id": 33,
"make_id": 6,
"name": "TrailBlazer",
"make": {
"id": 6,
"name": "Chevrolet"
}
}
},
{
"id": 40806,
"make_model_id": 33,
"year": 2007,
"name": "LS",
"description": "LS 4dr SUV 4WD (4.2L 6cyl 4A)",
"msrp": 27340,
"invoice": 25563,
"created": "2023-06-29T21:13:16-04:00",
"modified": "2023-06-29T21:13:16-04:00",
"make_model_trim_mileage": {
"id": 40806,
"make_model_trim_id": 40806,
"fuel_tank_capacity": "22.0",
"combined_mpg": 16,
"epa_city_mpg": 14,
"epa_highway_mpg": 20,
"range_city": 308,
"range_highway": 440,
"battery_capacity_electric": null,
"epa_time_to_charge_hr_240v_electric": null,
"epa_kwh_100_mi_electric": null,
"range_electric": null,
"epa_highway_mpg_electric": null,
"epa_city_mpg_electric": null,
"epa_combined_mpg_electric": null
},
"make_model_trim_engine": {
"id": 40806,
"make_model_trim_id": 40806,
"engine_type": "gas",
"fuel_type": "regular unleaded",
"cylinders": "I6",
"size": "4.2",
"horsepower_hp": 291,
"horsepower_rpm": 6000,
"torque_ft_lbs": 277,
"torque_rpm": 4800,
"valves": 24,
"valve_timing": "Variable",
"cam_type": "Double overhead cam (DOHC)",
"drive_type": "four wheel drive",
"transmission": "4-speed automatic"
},
"make_model_trim_body": {
"id": 40806,
"make_model_trim_id": 40806,
"type": "SUV",
"doors": 5,
"length": "191.8",
"width": "74.7",
"seats": 5,
"height": "72.5",
"wheel_base": "113.0",
"front_track": "62.7",
"rear_track": "62.0",
"ground_clearance": "7.8",
"cargo_capacity": "43.7",
"max_cargo_capacity": "80.1",
"curb_weight": 4523,
"gross_weight": 5750,
"max_payload": 1227,
"max_towing_capacity": 6600
},
"make_model": {
"id": 33,
"make_id": 6,
"name": "TrailBlazer",
"make": {
"id": 6,
"name": "Chevrolet"
}
}
},
{
"id": 40807,
"make_model_id": 33,
"year": 2007,
"name": "LT",
"description": "LT 4dr SUV (4.2L 6cyl 4A)",
"msrp": 27970,
"invoice": 26152,
"created": "2023-06-29T21:13:16-04:00",
"modified": "2023-06-29T21:13:16-04:00",
"make_model_trim_mileage": {
"id": 40807,
"make_model_trim_id": 40807,
"fuel_tank_capacity": "22.0",
"combined_mpg": 16,
"epa_city_mpg": 14,
"epa_highway_mpg": 20,
"range_city": 308,
"range_highway": 440,
"battery_capacity_electric": null,
"epa_time_to_charge_hr_240v_electric": null,
"epa_kwh_100_mi_electric": null,
"range_electric": null,
"epa_highway_mpg_electric": null,
"epa_city_mpg_electric": null,
"epa_combined_mpg_electric": null
},
"make_model_trim_engine": {
"id": 40807,
"make_model_trim_id": 40807,
"engine_type": "gas",
"fuel_type": "regular unleaded",
"cylinders": "I6",
"size": "4.2",
"horsepower_hp": 291,
"horsepower_rpm": 6000,
"torque_ft_lbs": 277,
"torque_rpm": 4800,
"valves": 24,
"valve_timing": "Variable",
"cam_type": "Double overhead cam (DOHC)",
"drive_type": "rear wheel drive",
"transmission": "4-speed automatic"
},
"make_model_trim_body": {
"id": 40807,
"make_model_trim_id": 40807,
"type": "SUV",
"doors": 5,
"length": "191.8",
"width": "74.7",
"seats": 5,
"height": null,
"wheel_base": "113.0",
"front_track": "62.7",
"rear_track": "62.0",
"ground_clearance": "7.8",
"cargo_capacity": "43.7",
"max_cargo_capacity": "80.1",
"curb_weight": 4356,
"gross_weight": 5550,
"max_payload": 1194,
"max_towing_capacity": 6800
},
"make_model": {
"id": 33,
"make_id": 6,
"name": "TrailBlazer",
"make": {
"id": 6,
"name": "Chevrolet"
}
}
},
{
"id": 40808,
"make_model_id": 33,
"year": 2007,
"name": "LT",
"description": "LT 4dr SUV 4WD (4.2L 6cyl 4A)",
"msrp": 30210,
"invoice": 28246,
"created": "2023-06-29T21:13:16-04:00",
"modified": "2023-06-29T21:13:16-04:00",
"make_model_trim_mileage": {
"id": 40808,
"make_model_trim_id": 40808,
"fuel_tank_capacity": "22.0",
"combined_mpg": 16,
"epa_city_mpg": 14,
"epa_highway_mpg": 20,
"range_city": 308,
"range_highway": 440,
"battery_capacity_electric": null,
"epa_time_to_charge_hr_240v_electric": null,
"epa_kwh_100_mi_electric": null,
"range_electric": null,
"epa_highway_mpg_electric": null,
"epa_city_mpg_electric": null,
"epa_combined_mpg_electric": null
},
"make_model_trim_engine": {
"id": 40808,
"make_model_trim_id": 40808,
"engine_type": "gas",
"fuel_type": "regular unleaded",
"cylinders": "I6",
"size": "4.2",
"horsepower_hp": 291,
"horsepower_rpm": 6000,
"torque_ft_lbs": 277,
"torque_rpm": 4800,
"valves": 24,
"valve_timing": "Variable",
"cam_type": "Double overhead cam (DOHC)",
"drive_type": "four wheel drive",
"transmission": "4-speed automatic"
},
"make_model_trim_body": {
"id": 40808,
"make_model_trim_id": 40808,
"type": "SUV",
"doors": 5,
"length": "191.8",
"width": "74.7",
"seats": 5,
"height": null,
"wheel_base": "113.0",
"front_track": "62.7",
"rear_track": "62.0",
"ground_clearance": "7.8",
"cargo_capacity": "43.7",
"max_cargo_capacity": "80.1",
"curb_weight": 4523,
"gross_weight": 5750,
"max_payload": 1227,
"max_towing_capacity": 6600
},
"make_model": {
"id": 33,
"make_id": 6,
"name": "TrailBlazer",
"make": {
"id": 6,
"name": "Chevrolet"
}
}
},
{
"id": 40809,
"make_model_id": 33,
"year": 2007,
"name": "SS",
"description": "SS 4dr SUV (6.0L 8cyl 4A)",
"msrp": 31320,
"invoice": 29284,
"created": "2023-06-29T21:13:16-04:00",
"modified": "2023-06-29T21:13:16-04:00",
"make_model_trim_mileage": {
"id": 40809,
"make_model_trim_id": 40809,
"fuel_tank_capacity": "22.0",
"combined_mpg": 15,
"epa_city_mpg": 13,
"epa_highway_mpg": 17,
"range_city": 286,
"range_highway": 374,
"battery_capacity_electric": null,
"epa_time_to_charge_hr_240v_electric": null,
"epa_kwh_100_mi_electric": null,
"range_electric": null,
"epa_highway_mpg_electric": null,
"epa_city_mpg_electric": null,
"epa_combined_mpg_electric": null
},
"make_model_trim_engine": {
"id": 40809,
"make_model_trim_id": 40809,
"engine_type": "gas",
"fuel_type": "premium unleaded (required)",
"cylinders": "V8",
"size": "6.0",
"horsepower_hp": 395,
"horsepower_rpm": 5400,
"torque_ft_lbs": 400,
"torque_rpm": 4400,
"valves": 16,
"valve_timing": null,
"cam_type": "Overhead valves (OHV)",
"drive_type": "rear wheel drive",
"transmission": "4-speed automatic"
},
"make_model_trim_body": {
"id": 40809,
"make_model_trim_id": 40809,
"type": "SUV",
"doors": 5,
"length": "191.8",
"width": "74.7",
"seats": 5,
"height": "67.8",
"wheel_base": "113.0",
"front_track": "62.7",
"rear_track": "62.0",
"ground_clearance": "7.8",
"cargo_capacity": "43.7",
"max_cargo_capacity": "80.1",
"curb_weight": 4496,
"gross_weight": 6001,
"max_payload": 1505,
"max_towing_capacity": 6800
},
"make_model": {
"id": 33,
"make_id": 6,
"name": "TrailBlazer",
"make": {
"id": 6,
"name": "Chevrolet"
}
}
},
{
"id": 40810,
"make_model_id": 33,
"year": 2007,
"name": "SS",
"description": "SS 4dr SUV AWD (6.0L 8cyl 4A)",
"msrp": 33620,
"invoice": 31435,
"created": "2023-06-29T21:13:16-04:00",
"modified": "2023-06-29T21:13:16-04:00",
"make_model_trim_mileage": {
"id": 40810,
"make_model_trim_id": 40810,
"fuel_tank_capacity": "22.0",
"combined_mpg": 14,
"epa_city_mpg": 12,
"epa_highway_mpg": 16,
"range_city": 264,
"range_highway": 352,
"battery_capacity_electric": null,
"epa_time_to_charge_hr_240v_electric": null,
"epa_kwh_100_mi_electric": null,
"range_electric": null,
"epa_highway_mpg_electric": null,
"epa_city_mpg_electric": null,
"epa_combined_mpg_electric": null
},
"make_model_trim_engine": {
"id": 40810,
"make_model_trim_id": 40810,
"engine_type": "gas",
"fuel_type": "premium unleaded (required)",
"cylinders": "V8",
"size": "6.0",
"horsepower_hp": 395,
"horsepower_rpm": 5400,
"torque_ft_lbs": 400,
"torque_rpm": 4400,
"valves": 16,
"valve_timing": null,
"cam_type": "Overhead valves (OHV)",
"drive_type": "all wheel drive",
"transmission": "4-speed automatic"
},
"make_model_trim_body": {
"id": 40810,
"make_model_trim_id": 40810,
"type": "SUV",
"doors": 5,
"length": "191.8",
"width": "74.7",
"seats": 5,
"height": "67.8",
"wheel_base": "113.0",
"front_track": "62.7",
"rear_track": "62.0",
"ground_clearance": "7.8",
"cargo_capacity": "43.7",
"max_cargo_capacity": "80.1",
"curb_weight": 4663,
"gross_weight": 6001,
"max_payload": 1338,
"max_towing_capacity": 6600
},
"make_model": {
"id": 33,
"make_id": 6,
"name": "TrailBlazer",
"make": {
"id": 6,
"name": "Chevrolet"
}
}
},
{
"id": 40812,
"make_model_id": 33,
"year": 2007,
"name": "SS",
"description": "SS 4dr SUV AWD w/3SS (6.0L 8cyl 4A)",
"msrp": 37125,
"invoice": 34712,
"created": "2023-06-29T21:13:16-04:00",
"modified": "2023-06-29T21:13:16-04:00",
"make_model_trim_mileage": {
"id": 40812,
"make_model_trim_id": 40812,
"fuel_tank_capacity": "22.0",
"combined_mpg": 14,
"epa_city_mpg": 12,
"epa_highway_mpg": 16,
"range_city": 264,
"range_highway": 352,
"battery_capacity_electric": null,
"epa_time_to_charge_hr_240v_electric": null,
"epa_kwh_100_mi_electric": null,
"range_electric": null,
"epa_highway_mpg_electric": null,
"epa_city_mpg_electric": null,
"epa_combined_mpg_electric": null
},
"make_model_trim_engine": {
"id": 40812,
"make_model_trim_id": 40812,
"engine_type": "gas",
"fuel_type": "premium unleaded (required)",
"cylinders": "V8",
"size": "6.0",
"horsepower_hp": 395,
"horsepower_rpm": 5400,
"torque_ft_lbs": 400,
"torque_rpm": 4400,
"valves": 16,
"valve_timing": null,
"cam_type": "Overhead valves (OHV)",
"drive_type": "all wheel drive",
"transmission": "4-speed automatic"
},
"make_model_trim_body": {
"id": 40812,
"make_model_trim_id": 40812,
"type": "SUV",
"doors": 5,
"length": "191.8",
"width": "74.7",
"seats": 5,
"height": "67.8",
"wheel_base": "113.0",
"front_track": "62.7",
"rear_track": "62.0",
"ground_clearance": "7.8",
"cargo_capacity": "43.7",
"max_cargo_capacity": "80.1",
"curb_weight": 4663,
"gross_weight": 6001,
"max_payload": 1338,
"max_towing_capacity": 6600
},
"make_model": {
"id": 33,
"make_id": 6,
"name": "TrailBlazer",
"make": {
"id": 6,
"name": "Chevrolet"
}
}
},
{
"id": 40811,
"make_model_id": 33,
"year": 2007,
"name": "SS",
"description": "SS 4dr SUV w/3SS (6.0L 8cyl 4A)",
"msrp": 34885,
"invoice": 32617,
"created": "2023-06-29T21:13:16-04:00",
"modified": "2023-06-29T21:13:16-04:00",
"make_model_trim_mileage": {
"id": 40811,
"make_model_trim_id": 40811,
"fuel_tank_capacity": "22.0",
"combined_mpg": 15,
"epa_city_mpg": 13,
"epa_highway_mpg": 17,
"range_city": 286,
"range_highway": 374,
"battery_capacity_electric": null,
"epa_time_to_charge_hr_240v_electric": null,
"epa_kwh_100_mi_electric": null,
"range_electric": null,
"epa_highway_mpg_electric": null,
"epa_city_mpg_electric": null,
"epa_combined_mpg_electric": null
},
"make_model_trim_engine": {
"id": 40811,
"make_model_trim_id": 40811,
"engine_type": "gas",
"fuel_type": "premium unleaded (required)",
"cylinders": "V8",
"size": "6.0",
"horsepower_hp": 395,
"horsepower_rpm": 5400,
"torque_ft_lbs": 400,
"torque_rpm": 4400,
"valves": 16,
"valve_timing": null,
"cam_type": "Overhead valves (OHV)",
"drive_type": "rear wheel drive",
"transmission": "4-speed automatic"
},
"make_model_trim_body": {
"id": 40811,
"make_model_trim_id": 40811,
"type": "SUV",
"doors": 5,
"length": "191.8",
"width": "74.7",
"seats": 5,
"height": "67.8",
"wheel_base": "113.0",
"front_track": "62.7",
"rear_track": "62.0",
"ground_clearance": "7.8",
"cargo_capacity": "43.7",
"max_cargo_capacity": "80.1",
"curb_weight": 4496,
"gross_weight": 6001,
"max_payload": 1505,
"max_towing_capacity": 6800
},
"make_model": {
"id": 33,
"make_id": 6,
"name": "TrailBlazer",
"make": {
"id": 6,
"name": "Chevrolet"
}
}
}
]
}

3
api/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

8
api/urls.py Normal file
View File

@ -0,0 +1,8 @@
from django.urls import path
from api import views
urlpatterns = [
path('cars/vin/', views.CarVINViewSet.as_view(), name='car_vin'),
path('login/', views.LoginView.as_view(), name='login'),
]

53
api/views.py Normal file
View File

@ -0,0 +1,53 @@
from django.views.decorators.csrf import csrf_exempt
from rest_framework import permissions, status, viewsets
from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import authenticate
from django.shortcuts import render
from . import models, serializers
from .services import get_car_data, get_from_cardatabase
from rest_framework.authtoken.models import Token
# from inventory.models import CustomUser
from django.utils.decorators import method_decorator
class LoginView(APIView):
permission_classes = [permissions.AllowAny,]
def post(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
if username is None or password is None:
return Response({'error': 'Please provide both username and password.'}, status=status.HTTP_400_BAD_REQUEST)
user = authenticate(username=username, password=password)
if not user:
return Response({'error': 'Invalid credentials.'}, status=status.HTTP_401_UNAUTHORIZED)
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key, 'user_id': user.id, 'username': user.username}, status=status.HTTP_200_OK)
class CarVINViewSet(APIView):
queryset = models.CarVIN.objects.all().order_by('-created')
serializer_class = serializers.CarVINSerializer
def get(self, request):
vin = models.CarVIN.objects.all()
serializer = serializers.CarVINSerializer(vin, many=True)
return Response(serializer.data)
def post(self, request):
serializer = serializers.CarVINSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
for key, value in serializer.validated_data.items():
print(key, value)
get_car_data(value)
# get_from_cardatabase(value)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

27
car_inventory/asgi.py Normal file
View File

@ -0,0 +1,27 @@
"""
ASGI config for car_inventory project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
# asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from api import routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'car_inventory.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
routing.websocket_urlpatterns
)
),
})

255
car_inventory/settings.py Normal file
View File

@ -0,0 +1,255 @@
"""
Django settings for car_inventory project.
Generated by 'django-admin startproject' using Django 5.0.6.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
import os
from django.utils.translation import gettext_lazy as _
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
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']
# Application definition
INSTALLED_APPS = [
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'inventory.apps.InventoryConfig',
'api.apps.ApiConfig',
# Other Apps to select what we need before deploy
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'debug_toolbar',
'silk',
'django_prometheus',
'django_tables2',
"django_bootstrap5",
"crispy_forms",
"crispy_bootstrap5",
"phonenumber_field",
"rest_framework",
'rest_framework.authtoken',
'django_extensions',
'djangoviz',
'django_ledger',
'djmoney',
'sslserver',
]
SITE_ID = 1
MIDDLEWARE = [
'django_prometheus.middleware.PrometheusBeforeMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'allauth.account.middleware.AccountMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'silk.middleware.SilkyMiddleware',
'django_prometheus.middleware.PrometheusAfterMiddleware',
]
ROOT_URLCONF = 'car_inventory.urls'
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, 'templates')],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"dj_shop_cart.context_processors.cart",
],
},
},
]
WSGI_APPLICATION = 'car_inventory.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django_prometheus.db.backends.postgresql",
"NAME": "secondhaikal",
"USER": "f95166",
"PASSWORD": "Kfsh&rc9788",
"HOST": "localhost",
"PORT": 5432,
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Authentications
LOGIN_REDIRECT_URL = '/'
ACCOUNT_LOGOUT_REDIRECT_URL = '/'
ACCOUNT_EMAIL_VERIFICATION = "none"
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
# 'rest_framework.authentication.SessionAuthentication',
# 'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.AllowAny',
],
}
# REST_AUTH = {
# 'USE_JWT': True,
# 'JWT_AUTH_COOKIE': 'jwt-auth',
# }
# Email backend
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
STATIC_URL = '/static/'
MEDIA_URL = '/images/'
STATICFILES_DIRS = [
BASE_DIR / 'static'
]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/images')
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
LANGUAGE_COOKIE_NAME = 'django_language'
LANGUAGE_CODE = "en"
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
('en', _('English')),
('ar', _('Arabic')),
]
TIME_ZONE = "Asia/Riyadh"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
SILKY_PYTHON_PROFILER = True
DJANGO_TABLES2_TEMPLATE = "django_tables2/bootstrap5-responsive.html"
DJANGO_TABLES2_TABLE_ATTRS = {
'class': 'table table-sm table-hover table-responsive-sm',
'thead': {
'class': 'table-secondary',
},
'tbody': {
'class': 'fw-light',
}
}
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
# DEFAULT_CURRENCY = 'SAR'
# API KEYS
OPENAI_API_KEY = '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'
APP_ID = '367974ed'
APP_KEY = '046b0412c1b4d3f8c39ec6375d6f3030'
CLIENT_ID = '94142c27-2536-47e9-8e28-9ca7728b9442'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'INFO', # Change to WARNING or ERROR as needed
},
'openai': {
'handlers': ['console'],
'level': 'WARNING', # Suppress detailed logs from OpenAI client
},
},
}

27
car_inventory/urls.py Normal file
View File

@ -0,0 +1,27 @@
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
from django.conf.urls.i18n import i18n_patterns
from inventory import views
import debug_toolbar
urlpatterns = [
path('__debug__/', include(debug_toolbar.urls)),
path('silk/', include('silk.urls', namespace='silk')),
path('api-auth/', include('rest_framework.urls')),
path('api/', include('api.urls')),
path('dj-rest-auth/', include('dj_rest_auth.urls')),
]
urlpatterns += i18n_patterns(
path('admin/', admin.site.urls),
path('switch_language/', views.switch_language, name='switch_language'),
path('accounts/', include('allauth.urls')),
path('prometheus/', include('django_prometheus.urls')),
path('', include('inventory.urls')),
path('ledger/', include('django_ledger.urls', namespace='django_ledger')),
)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

16
car_inventory/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for car_inventory project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'car_inventory.settings')
application = get_wsgi_application()

4012
carmake_backup.json Normal file

File diff suppressed because it is too large Load Diff

45641
carmodel_backup.json Normal file

File diff suppressed because it is too large Load Diff

141095
carserie_backup.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,848 @@
[
{
"model": "inventory.carspecification",
"pk": 1,
"fields": {
"name": "Bodywork",
"arabic_name": "هيكل السيارة",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 2,
"fields": {
"name": "Body type",
"arabic_name": "نوع الهيكل",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 3,
"fields": {
"name": "Number of doors",
"arabic_name": "عدد الأبواب",
"id_parent": 1549
}
},
{
"model": "inventory.carspecification",
"pk": 4,
"fields": {
"name": "Number of seater",
"arabic_name": "عدد المقاعد",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 5,
"fields": {
"name": "Length",
"arabic_name": "الطول",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 6,
"fields": {
"name": "Width",
"arabic_name": "العرض",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 7,
"fields": {
"name": "Height",
"arabic_name": "الارتفاع",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 8,
"fields": {
"name": "Wheelbase",
"arabic_name": "قاعدة العجلات",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 9,
"fields": {
"name": "Front track",
"arabic_name": "المسار الأمامي",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 10,
"fields": {
"name": "Rear track",
"arabic_name": "المسار الخلفي",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 11,
"fields": {
"name": "Engine",
"arabic_name": "المحرك",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 12,
"fields": {
"name": "Engine type",
"arabic_name": "نوع المحرك",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 13,
"fields": {
"name": "Capacity",
"arabic_name": "السعة",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 14,
"fields": {
"name": "Engine power",
"arabic_name": "قوة المحرك",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 15,
"fields": {
"name": "Max power at RPM",
"arabic_name": "القوة القصوى عند دورة في الدقيقة",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 16,
"fields": {
"name": "Maximum torque",
"arabic_name": "العزم الأقصى",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 17,
"fields": {
"name": "Injection type",
"arabic_name": "نوع الحقن",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 18,
"fields": {
"name": "Overhead camshaft",
"arabic_name": "عمود الحدبات العلوي",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 19,
"fields": {
"name": "Cylinder layout",
"arabic_name": "تخطيط الأسطوانة",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 20,
"fields": {
"name": "Number of cylinders",
"arabic_name": "عدد الأسطوانات",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 21,
"fields": {
"name": "Compression ratio",
"arabic_name": "نسبة الضغط",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 22,
"fields": {
"name": "Fuel",
"arabic_name": "الوقود",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 23,
"fields": {
"name": "Gearbox",
"arabic_name": "(جيربوكس) علبة التروس",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 24,
"fields": {
"name": "Gearbox type",
"arabic_name": "نوع علبة التروس",
"id_parent": 23
}
},
{
"model": "inventory.carspecification",
"pk": 26,
"fields": {
"name": "Number of gear",
"arabic_name": "عدد السرعات",
"id_parent": 23
}
},
{
"model": "inventory.carspecification",
"pk": 27,
"fields": {
"name": "Drive wheels",
"arabic_name": "عجلات القيادة",
"id_parent": 23
}
},
{
"model": "inventory.carspecification",
"pk": 29,
"fields": {
"name": "Front brakes",
"arabic_name": "الفرامل الأمامية",
"id_parent": 40
}
},
{
"model": "inventory.carspecification",
"pk": 30,
"fields": {
"name": "Rear brakes",
"arabic_name": "الفرامل الخلفية",
"id_parent": 40
}
},
{
"model": "inventory.carspecification",
"pk": 31,
"fields": {
"name": "Operating characteristics",
"arabic_name": "خصائص التشغيل",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 32,
"fields": {
"name": "Max speed",
"arabic_name": "السرعة القصوى",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 33,
"fields": {
"name": "Acceleration (0-100 km/h)",
"arabic_name": "التسارع (من ٠ إلى ١٠٠ كم/ساعة)",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 34,
"fields": {
"name": "Curb weight",
"arabic_name": "وزن الرصيف",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 35,
"fields": {
"name": "Fuel tank capacity",
"arabic_name": "سعة خزان الوقود",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 36,
"fields": {
"name": "Wheel size",
"arabic_name": "حجم العجلة",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 37,
"fields": {
"name": "Emission standards",
"arabic_name": "معايير الانبعاثات",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 38,
"fields": {
"name": "Ground clearance",
"arabic_name": "المسافة الفارغة عن الأرض",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 39,
"fields": {
"name": "Valves per cylinder",
"arabic_name": "صمامات لكل اسطوانة",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 40,
"fields": {
"name": "Suspension and brakes",
"arabic_name": "التعليق والفرامل",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 41,
"fields": {
"name": "Front suspension",
"arabic_name": "تعليق أمامي",
"id_parent": 40
}
},
{
"model": "inventory.carspecification",
"pk": 42,
"fields": {
"name": "Back suspension",
"arabic_name": "تعليق الخلفي",
"id_parent": 40
}
},
{
"model": "inventory.carspecification",
"pk": 44,
"fields": {
"name": "Max trunk capacity",
"arabic_name": "الحد الأقصى لسعة صندوق السيارة",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 45,
"fields": {
"name": "Min trunk capacity",
"arabic_name": "الحد الأدنى لسعة صندوق السيارة",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 46,
"fields": {
"name": "Boost type",
"arabic_name": "نوع الدفع",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 47,
"fields": {
"name": "Cylinder bore",
"arabic_name": "قطر الأسطوانة",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 48,
"fields": {
"name": "Stroke cycle",
"arabic_name": "دورة الشوط",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 49,
"fields": {
"name": "Bore/stroke ratio",
"arabic_name": "نسبة القطر إلى الشوط",
"id_parent": 23
}
},
{
"model": "inventory.carspecification",
"pk": 50,
"fields": {
"name": "City driving fuel consumption per 100 km",
"arabic_name": "استهلاك الوقود داخل المدينة لكل ١٠٠ كيلومتر",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 51,
"fields": {
"name": "Highway driving fuel consumption per 100 km",
"arabic_name": "استهلاك الوقود على الطريق السريع لكل ١٠٠ كيلومتر",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 52,
"fields": {
"name": "Mixed driving fuel consumption per 100 km",
"arabic_name": "استهلاك الوقود لكل ١٠٠ كيلومتر في القيادة المختطلة",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 53,
"fields": {
"name": "Steering",
"arabic_name": "التوجيه",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 54,
"fields": {
"name": "Steering type",
"arabic_name": "نوع التوجيه",
"id_parent": 53
}
},
{
"model": "inventory.carspecification",
"pk": 55,
"fields": {
"name": "Engine model",
"arabic_name": "نموذج المحرك",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 56,
"fields": {
"name": "Electric motor power",
"arabic_name": "قوة المحرك الكهربائي",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 57,
"fields": {
"name": "Turning circle",
"arabic_name": "دائرة الالتفاف",
"id_parent": 23
}
},
{
"model": "inventory.carspecification",
"pk": 58,
"fields": {
"name": "Full weight",
"arabic_name": "الوزن الكامل",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 59,
"fields": {
"name": "Disc size",
"arabic_name": "حجم القرص",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 60,
"fields": {
"name": "Total power output",
"arabic_name": "إجمالي قوة الإخراج",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 61,
"fields": {
"name": "Engine placement",
"arabic_name": "موضع المحرك",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 62,
"fields": {
"name": "Cruising range",
"arabic_name": "مدى السير",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 63,
"fields": {
"name": "Full cycle charge",
"arabic_name": "شحن دورة كاملة",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 66,
"fields": {
"name": "Car width with mirrors",
"arabic_name": "عرض السيارة مع المرايا",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1521,
"fields": {
"name": "Cylinder bore and stroke cycle",
"arabic_name": "دورة حجم الاسطوانة والشوط",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 1549,
"fields": {
"name": "General information",
"arabic_name": "معلومات عامة",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 1550,
"fields": {
"name": "Volume and weight",
"arabic_name": "الحجم والوزن",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 1551,
"fields": {
"name": "Security",
"arabic_name": "الأمان",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 1552,
"fields": {
"name": "Country",
"arabic_name": "الدولة",
"id_parent": 1549
}
},
{
"model": "inventory.carspecification",
"pk": 1553,
"fields": {
"name": "Car class",
"arabic_name": "فئة السيارة",
"id_parent": 1549
}
},
{
"model": "inventory.carspecification",
"pk": 1554,
"fields": {
"name": "Clearance",
"arabic_name": "إرتفاع السيارة عن الارض",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1555,
"fields": {
"name": "Front track width",
"arabic_name": "عرض المسار الأمامي للسيارة",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1556,
"fields": {
"name": "Back track width",
"arabic_name": "عرض المسار الخلفي للسيارة",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1558,
"fields": {
"name": "Max power (h.p.)",
"arabic_name": "القوة القصوى (حصان)",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 1559,
"fields": {
"name": "Max power (kW)",
"arabic_name": "القوة القصوى (كيلوواط)",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 1560,
"fields": {
"name": "Model assembly",
"arabic_name": "تجميع الطراز",
"id_parent": 1549
}
},
{
"model": "inventory.carspecification",
"pk": 1561,
"fields": {
"name": "CO2 emissions",
"arabic_name": "انبعاثات ثاني أكسيد الكربون",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 1562,
"fields": {
"name": "Safety assessment",
"arabic_name": "تقييم السلامة",
"id_parent": 1551
}
},
{
"model": "inventory.carspecification",
"pk": 1563,
"fields": {
"name": "Rating name",
"arabic_name": "اسم التقييم",
"id_parent": 1551
}
},
{
"model": "inventory.carspecification",
"pk": 1564,
"fields": {
"name": "Turnover of maximum torque",
"arabic_name": "تحول العزم الأقصى",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 1565,
"fields": {
"name": "Payload",
"arabic_name": "الحمولة",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1566,
"fields": {
"name": "Presence of intercooler",
"arabic_name": "وجود مبرد بيني",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 1567,
"fields": {
"name": "Trailer load (with brakes)",
"arabic_name": "حمولة المقطورة (مع الفرامل)",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1568,
"fields": {
"name": "Front/rear axle load",
"arabic_name": "حمولة محور العجلات الأمامي / الخلفي",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1569,
"fields": {
"name": "Loading height",
"arabic_name": "ارتفاع التحميل",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1570,
"fields": {
"name": "Cargo compartment (Length x Width x Height)",
"arabic_name": "حجرة البضائع (الطول x العرض x الارتفاع)",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1571,
"fields": {
"name": "Cargo compartment volume",
"arabic_name": "حجم حيز التحميل في السيارة",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1631,
"fields": {
"name": "Accumulator battery",
"arabic_name": "البطارية المجمعة",
"id_parent": null
}
},
{
"model": "inventory.carspecification",
"pk": 1632,
"fields": {
"name": "Battery capacity",
"arabic_name": "سعة البطارية",
"id_parent": 1631
}
},
{
"model": "inventory.carspecification",
"pk": 1634,
"fields": {
"name": "Electric power reserve",
"arabic_name": "احتياطي الطاقة الكهربائية",
"id_parent": 1631
}
},
{
"model": "inventory.carspecification",
"pk": 1635,
"fields": {
"name": "Charging time",
"arabic_name": "وقت الشحن",
"id_parent": 1631
}
},
{
"model": "inventory.carspecification",
"pk": 1636,
"fields": {
"name": "Fuel consumption city/highway/mixed, l",
"arabic_name": "استهلاك الوقود في المدينة / الطريق السريع / المختلط، لتر",
"id_parent": 31
}
},
{
"model": "inventory.carspecification",
"pk": 1641,
"fields": {
"name": "Rudder location",
"arabic_name": "موقع الدفة",
"id_parent": 53
}
},
{
"model": "inventory.carspecification",
"pk": 1642,
"fields": {
"name": "Dimensions",
"arabic_name": "الأبعاد",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1644,
"fields": {
"name": "Pitch Circle Diameter",
"arabic_name": "قطر دائرة التركيب",
"id_parent": 1
}
},
{
"model": "inventory.carspecification",
"pk": 1645,
"fields": {
"name": "Engine code",
"arabic_name": "كود المحرك",
"id_parent": 11
}
},
{
"model": "inventory.carspecification",
"pk": 1646,
"fields": {
"name": "Disc sizes",
"arabic_name": "أحجام الأقراص",
"id_parent": 1
}
}
]

Binary file not shown.

940678
cartrim_backup.json Normal file

File diff suppressed because it is too large Load Diff

20
docs/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

35
docs/make.bat Normal file
View File

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

28
docs/source/conf.py Normal file
View File

@ -0,0 +1,28 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'Haikal'
copyright = '2024, Marwan Alwali'
author = 'Marwan Alwali'
release = '01/11/2024'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = []
templates_path = ['_templates']
exclude_patterns = []
language = '[en,ar]'
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'alabaster'
html_static_path = ['_static']

17
docs/source/index.rst Normal file
View File

@ -0,0 +1,17 @@
.. Haikal documentation master file, created by
sphinx-quickstart on Mon Nov 25 16:28:43 2024.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Haikal documentation
====================
Add your content using ``reStructuredText`` syntax. See the
`reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_
documentation for details.
.. toctree::
:maxdepth: 2
:caption: Contents:

1565
haikalna.py Normal file

File diff suppressed because it is too large Load Diff

BIN
inventory/.DS_Store vendored Normal file

Binary file not shown.

0
inventory/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

76
inventory/admin.py Normal file
View File

@ -0,0 +1,76 @@
from django.contrib import admin
from . import models
admin.site.register(models.Dealer)
admin.site.register(models.Vendor)
admin.site.register(models.Customer)
admin.site.register(models.Car)
admin.site.register(models.CarFinance)
admin.site.register(models.CarColors)
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')
search_fields = ('name', 'arabic_name')
class Meta:
verbose_name = "Car Make"
ordering = ('name',)
@admin.register(models.CarModel)
class CarModelAdmin(admin.ModelAdmin):
list_display = ('name', 'arabic_name', 'id_car_make', 'get_is_sa_import')
search_fields = ('name', 'arabic_name')
list_filter = ('id_car_make__is_sa_import', 'id_car_make')
sortable_by = ['name', 'arabic_name', 'id_car_make']
def get_is_sa_import(self, obj):
return obj.id_car_make.is_sa_import
get_is_sa_import.boolean = True
get_is_sa_import.short_description = 'Is SA Import'
class Meta:
verbose_name = "Car Model"
ordering = ('name',)
@admin.register(models.CarSerie)
class CarSeriesAdmin(admin.ModelAdmin):
list_display = ('name', 'arabic_name', 'id_car_model')
search_fields = ('name', 'id_car_model__name')
list_filter = ('id_car_model__id_car_make__is_sa_import',
'id_car_model__id_car_make__name',)
class Meta:
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')
class Meta:
verbose_name = "Car Trim"
@admin.register(models.CarSpecification)
class CarSpecificationAdmin(admin.ModelAdmin):
list_display = ('name', 'arabic_name', 'id_parent')
search_fields = ('name', 'id_parent')
list_filter = ('id_parent',)
class Meta:
verbose_name = "Car Specification"

6
inventory/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class InventoryConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'inventory'

View File

@ -0,0 +1,9 @@
def breadcrumbs(request):
breadcrumbs = []
path = request.path.strip('/').split('/')
for i in range(len(path)):
url = '/' + '/'.join(path[:i+1]) + '/'
breadcrumbs.append({'name': path[i].capitalize(), 'url': url})
return {'breadcrumbs': breadcrumbs}

1
inventory/filters.py Normal file
View File

@ -0,0 +1 @@

163
inventory/forms.py Normal file
View File

@ -0,0 +1,163 @@
from django import forms
from .mixins import AddClassMixin
from .models import (
Dealer,
# Branch,
Vendor,
Customer,
Car,
CarFinance,
CustomCard,
CarRegistration,
CarColors
)
from django.contrib.contenttypes.forms import generic_inlineformset_factory
from django.utils.translation import gettext_lazy as _
# Dealer Form
class DealerForm(forms.ModelForm):
class Meta:
model = Dealer
fields = ['crn', 'vrn', 'arabic_name', 'name', 'phone_number', 'address', 'logo']
# Customer Form
class CustomerForm(forms.ModelForm, AddClassMixin):
class Meta:
model = Customer
fields = [
'first_name', 'middle_name', 'last_name', 'email',
'national_id', 'phone_number', 'address'
]
class CarForm(forms.ModelForm, AddClassMixin, ):
class Meta:
model = Car
fields = [
'vin', 'id_car_make', 'id_car_model',
'year', 'id_car_serie', 'id_car_trim',
'stock_type', 'remarks', 'mileage', 'receiving_date', 'vendor'
]
widgets = {
'receiving_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'remarks': forms.Textarea(attrs={'rows': 2}),
}
def __init__(self, *args, **kwargs):
dealer = kwargs.pop('dealer', None)
super().__init__(*args, **kwargs)
# if dealer:
# self.fields['branch'].queryset = Branch.objects.filter(dealer=dealer)
if 'id_car_make' in self.fields:
queryset = self.fields['id_car_make'].queryset
self.fields['id_car_make'].choices = [
(obj.id_car_make, obj.get_local_name()) for obj in queryset
]
class CarUpdateForm(forms.ModelForm, AddClassMixin):
class Meta:
model = Car
fields = ['vendor', 'status', 'stock_type', 'mileage', 'receiving_date', 'remarks']
widgets = {
'receiving_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'remarks': forms.Textarea(attrs={'rows': 2}),
}
def __init__(self, *args, **kwargs):
dealer = kwargs.pop('dealer', None)
super().__init__(*args, **kwargs)
# if dealer and 'branch' in self.fields:
# self.fields['branch'].queryset = Branch.objects.filter(dealer=dealer)
# self.fields['branch'].choices = [
# (branch.id, branch.get_local_name()) for branch in self.fields['branch'].queryset
# ]
if 'vendor' in self.fields:
queryset = self.fields['vendor'].queryset
if queryset:
self.fields['vendor'].choices = [
(vendor.id, vendor.get_local_name()) for vendor in queryset
]
class CarFinanceForm(AddClassMixin, forms.ModelForm):
profit_margin_percentage = forms.DecimalField(
max_digits=10,
decimal_places=2,
min_value=0,
max_value=100,
label="Profit Margin",
required=True,
widget=forms.NumberInput(attrs={'min': '0', 'max': '100', 'step': '0.01'})
)
vat_rate_percentage = forms.DecimalField(
max_digits=10,
decimal_places=2,
min_value=0,
max_value=100,
label="Vat Rate",
required=True,
widget=forms.NumberInput(attrs={'min': '0', 'max': '100', 'step': '0.01'})
)
class Meta:
model = CarFinance
fields = ['cost_price']
def __init__(self, *args, **kwargs):
super(CarFinanceForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
# Convert profit_margin from decimal to percentage for initial display
self.fields['profit_margin_percentage'].initial = self.instance.profit_margin * 100
self.fields['vat_rate_percentage'].initial = self.instance.vat_rate * 100
def clean_profit_margin_percentage(self):
profit_margin_percentage = self.cleaned_data['profit_margin_percentage']
if not (0 <= profit_margin_percentage <= 100):
raise forms.ValidationError('Profit margin must be between 0 and 100.')
return profit_margin_percentage
def clean_vat_rate_percentage(self):
vat_rate_percentage = self.cleaned_data['vat_rate_percentage']
if not (0 <= vat_rate_percentage <= 100):
raise forms.ValidationError('vat rate must be between 0 and 100.')
return vat_rate_percentage
def save(self, commit=True):
instance = super(CarFinanceForm, self).save(commit=False)
profit_margin_percentage = self.cleaned_data['profit_margin_percentage']
vat_rate_percentage = self.cleaned_data['vat_rate_percentage']
instance.profit_margin = profit_margin_percentage / 100
instance.vat_rate = vat_rate_percentage / 100
if commit:
instance.save()
return instance
# Custom Card Form
class CustomCardForm(forms.ModelForm):
custom_date = forms.DateTimeField(
widget=forms.DateInput(attrs={'type': 'date'}),
label=_("Custom Date"),
)
class Meta:
model = CustomCard
fields = ['custom_number', 'custom_date']
# Car Registration Form
class CarRegistrationForm(forms.ModelForm):
class Meta:
model = CarRegistration
fields = [
'car', 'plate_number', 'text1', 'text2', 'text3', 'registration_date'
]

BIN
inventory/management/.DS_Store vendored Normal file

Binary file not shown.

View File

BIN
inventory/management/commands/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

0
inventory/middleware.py Normal file
View File

View File

@ -0,0 +1,293 @@
# Generated by Django 5.1.4 on 2024-12-04 23:43
import django.db.models.deletion
import inventory.mixins
import phonenumber_field.modelfields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Car',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')),
('year', models.IntegerField(verbose_name='Year')),
('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged')], default='available', max_length=10, verbose_name='Status')),
('stock_type', models.CharField(choices=[('new', 'New'), ('used', 'Used')], default='new', max_length=10, verbose_name='Stock Type')),
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')),
('receiving_date', models.DateTimeField(verbose_name='Receiving Date')),
],
options={
'verbose_name': 'Car',
'verbose_name_plural': 'Cars',
},
),
migrations.CreateModel(
name='CarMake',
fields=[
('id_car_make', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')),
('is_sa_import', models.BooleanField(default=False)),
],
options={
'verbose_name': 'Make',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='CarFinance',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')),
('profit_margin', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Profit Margin')),
('selling_price', models.DecimalField(decimal_places=2, editable=False, max_digits=14, verbose_name='Selling Price')),
('vat_rate', models.DecimalField(decimal_places=2, default=0.15, max_digits=10, verbose_name='VAT Rate')),
('vat_amount', models.DecimalField(decimal_places=2, editable=False, max_digits=12, verbose_name='VAT Amount')),
('total', models.DecimalField(decimal_places=2, editable=False, max_digits=14, verbose_name='Total Amount')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')),
],
options={
'verbose_name': 'Car Financial Details',
},
),
migrations.AddField(
model_name='car',
name='id_car_make',
field=models.ForeignKey(blank=True, db_column='id_car_make', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make'),
),
migrations.CreateModel(
name='CarModel',
fields=[
('id_car_model', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('id_car_make', models.ForeignKey(db_column='id_car_make', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake')),
],
options={
'verbose_name': 'Model',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.AddField(
model_name='car',
name='id_car_model',
field=models.ForeignKey(blank=True, db_column='id_car_model', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'),
),
migrations.CreateModel(
name='CarRegistration',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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')),
('text3', models.CharField(max_length=1, verbose_name='Text 3')),
('registration_date', models.DateTimeField(verbose_name='Registration Date')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')),
],
options={
'verbose_name': 'Registration',
'verbose_name_plural': 'Registrations',
},
),
migrations.CreateModel(
name='CarSerie',
fields=[
('id_car_serie', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('id_car_model', models.ForeignKey(db_column='id_car_model', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel')),
],
options={
'verbose_name': 'Series',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.AddField(
model_name='car',
name='id_car_serie',
field=models.ForeignKey(blank=True, db_column='id_car_serie', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie', verbose_name='Series'),
),
migrations.CreateModel(
name='CarSpecification',
fields=[
('id_car_specification', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')),
],
options={
'verbose_name': 'Specification',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='CarTrim',
fields=[
('id_car_trim', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('start_production_year', models.IntegerField(blank=True, null=True)),
('end_production_year', models.IntegerField(blank=True, null=True)),
('id_car_serie', models.ForeignKey(db_column='id_car_serie', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie')),
],
options={
'verbose_name': 'Trim',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='CarSpecificationValue',
fields=[
('id_car_specification_value', models.AutoField(primary_key=True, serialize=False)),
('value', models.CharField(max_length=500)),
('unit', models.CharField(blank=True, max_length=255, null=True)),
('id_car_specification', models.ForeignKey(db_column='id_car_specification', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')),
('id_car_trim', models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim')),
],
options={
'verbose_name': 'Specification Value',
},
),
migrations.AddField(
model_name='car',
name='id_car_trim',
field=models.ForeignKey(blank=True, db_column='id_car_trim', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim', verbose_name='Trim'),
),
migrations.CreateModel(
name='CustomCard',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('custom_number', models.CharField(max_length=255, verbose_name='Custom Number')),
('custom_date', models.DateTimeField(verbose_name='Custom Date')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='custom_cards', to='inventory.car', verbose_name='Car')),
],
options={
'verbose_name': 'Custom Card',
'verbose_name_plural': 'Custom Cards',
},
),
migrations.CreateModel(
name='Dealer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('logo', models.ImageField(blank=True, null=True, upload_to='logos/users', verbose_name='Logo')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Dealer',
'verbose_name_plural': 'Dealers',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Customer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=50, verbose_name='First Name')),
('middle_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='Middle Name')),
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')),
],
options={
'verbose_name': 'Customer',
'verbose_name_plural': 'Customers',
},
),
migrations.AddField(
model_name='car',
name='dealer',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'),
),
migrations.CreateModel(
name='ExteriorColors',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='exteriorcolor', to='inventory.car')),
],
options={
'verbose_name': 'Exterior Color',
'verbose_name_plural': 'Exterior Colors',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='InteriorColors',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interiorcolor', to='inventory.car', verbose_name='Car')),
],
options={
'verbose_name': 'Interior Color',
'verbose_name_plural': 'Interior Colors',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Vendor',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')),
],
options={
'verbose_name': 'Vendor',
'verbose_name_plural': 'Vendors',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.AddField(
model_name='car',
name='vendor',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.vendor', verbose_name='Vendor'),
),
migrations.CreateModel(
name='CarReservation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('reserved_at', models.DateTimeField(auto_now_add=True)),
('reserved_until', models.DateTimeField()),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.car')),
('reserved_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-reserved_at'],
'unique_together': {('car', 'reserved_until')},
},
),
]

View File

@ -0,0 +1,39 @@
# Generated by Django 5.1.4 on 2024-12-06 14:30
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='interiorcolors',
name='car',
),
migrations.CreateModel(
name='CarColors',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
('color_type', models.CharField(choices=[('exterior', 'Exterior'), ('interior', 'Interior')], default='exterior', max_length=10, verbose_name='Color Type')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.car')),
],
options={
'verbose_name': 'Color',
'verbose_name_plural': 'Colors',
},
),
migrations.DeleteModel(
name='ExteriorColors',
),
migrations.DeleteModel(
name='InteriorColors',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2024-12-08 08:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_remove_interiorcolors_car_carcolors_and_more'),
]
operations = [
migrations.AlterField(
model_name='customcard',
name='custom_date',
field=models.DateField(verbose_name='Custom Date'),
),
]

View File

34
inventory/mixins.py Normal file
View File

@ -0,0 +1,34 @@
from django import forms
from django.utils.translation import get_language
class AddClassMixin:
"""
Mixin for adding classes to a model.
"""
def add_class_to_fields(self):
"""
Adds the class to the fields of the model.
:return: class names form-control or form-select
"""
for field_name, field in self.fields.items():
if isinstance(field.widget, forms.Select):
existing_classes = field.widget.attrs.get('class', '')
field.widget.attrs['class'] = f"{existing_classes} form-select form-select-sm".strip()
else:
existing_classes = field.widget.attrs.get('class', '')
field.widget.attrs['class'] = f"{existing_classes} form-control form-control-sm".strip()
class LocalizedNameMixin:
"""
Mixin to provide a reusable get_localized_name method.
"""
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)

503
inventory/models.py Normal file
View File

@ -0,0 +1,503 @@
from uuid import uuid4
from django.db import models, transaction
from django.contrib.auth.models import User
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django_ledger.models import (
VendorModel,
EntityModel,
EntityUnitModel,
ItemModel,
AccountModel,
ItemModelAbstract,
UnitOfMeasureModel,
CustomerModel,
ItemModelQuerySet,
)
from phonenumber_field.modelfields import PhoneNumberField
from django.contrib.contenttypes.models import ContentType
from decimal import Decimal
from django.utils.timezone import now
from .mixins import LocalizedNameMixin
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)
is_sa_import = models.BooleanField(default=False)
def __str__(self):
return self.name
class Meta:
verbose_name = "Make"
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')
name = models.CharField(max_length=255)
arabic_name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Meta:
verbose_name = "Model"
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')
name = models.CharField(max_length=255)
arabic_name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Meta:
verbose_name = "Series"
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')
name = models.CharField(max_length=255)
arabic_name = models.CharField(max_length=255)
start_production_year = models.IntegerField(blank=True, null=True)
end_production_year = models.IntegerField(blank=True, null=True)
def __str__(self):
return self.name
class Meta:
verbose_name = "Trim"
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)
def __str__(self):
return self.name
class Meta:
verbose_name = "Specification"
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')
value = models.CharField(max_length=500)
unit = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
return f"{self.id_car_specification.name}: {self.value} {self.unit}"
class Meta:
verbose_name = "Specification Value"
# Car Model
class CarStatusChoices(models.TextChoices):
AVAILABLE = 'available', _('Available')
SOLD = 'sold', _('Sold')
HOLD = 'hold', _('Hold')
DAMAGED = 'damaged', _('Damaged')
class CarStockTypeChoices(models.TextChoices):
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")
)
vendor = models.ForeignKey(
"Vendor",
models.DO_NOTHING,
null=True,
blank=True,
related_name='cars',
verbose_name=_("Vendor")
)
id_car_make = models.ForeignKey(
CarMake,
models.DO_NOTHING,
db_column='id_car_make',
null=True,
blank=True,
verbose_name=_("Make")
)
id_car_model = models.ForeignKey(
CarModel,
models.DO_NOTHING,
db_column='id_car_model',
null=True,
blank=True,
verbose_name=_("Model")
)
year = models.IntegerField(verbose_name=_("Year"))
id_car_serie = models.ForeignKey(
CarSerie,
models.DO_NOTHING,
db_column='id_car_serie',
null=True,
blank=True,
verbose_name=_("Series")
)
id_car_trim = models.ForeignKey(
CarTrim,
models.DO_NOTHING,
db_column='id_car_trim',
null=True,
blank=True,
verbose_name=_("Trim")
)
status = models.CharField(
max_length=10,
choices=CarStatusChoices.choices,
default=CarStatusChoices.AVAILABLE,
verbose_name=_("Status")
)
stock_type = models.CharField(
max_length=10,
choices=CarStockTypeChoices.choices,
default=CarStockTypeChoices.NEW,
verbose_name=_("Stock Type")
)
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
receiving_date = models.DateTimeField(verbose_name=_("Receiving Date"))
class Meta:
verbose_name = _("Car")
verbose_name_plural = _("Cars")
def __str__(self):
make = self.id_car_make.name if self.id_car_make else "Unknown Make"
model = self.id_car_model.name if self.id_car_model else "Unknown Model"
trim = self.id_car_trim.name if self.id_car_trim else "Unknown Trim"
return f"{self.year} - {make} - {model} - {trim}"
def is_reserved(self):
active_reservations = self.reservations.filter(reserved_until__gt=now())
return active_reservations.exists()
@property
def selling_price(self):
finance = self.finances.first()
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')
@property
def total(self):
finance = self.finances.first()
return finance.total if finance else Decimal('0.00')
class CarReservation(models.Model):
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()
def is_active(self):
return self.reserved_until > now()
class Meta:
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)
class Meta:
verbose_name = _("Car Financial Details")
def save(self, *args, **kwargs):
self.full_clean()
self.selling_price = self.cost_price * (1 + self.profit_margin)
self.vat_amount = self.selling_price * self.vat_rate
self.total = self.selling_price + self.vat_amount
super().save(*args, **kwargs)
def __str__(self):
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")
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"))
color_type = models.CharField(
max_length=10,
choices=ColorType.choices,
default=ColorType.EXTERIOR,
verbose_name=_("Color Type")
)
class Meta:
verbose_name = _("Color")
verbose_name_plural = _("Colors")
def __str__(self):
return f"{self.get_color_type_display()} - {self.name} ({self.rgb})"
# Custom Card Model
class CustomCard(models.Model):
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"))
class Meta:
verbose_name = _("Custom Card")
verbose_name_plural = _("Custom Cards")
def __str__(self):
return f"{self.car} - {self.custom_number}"
# Car Registration Model
class CarRegistration(models.Model):
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"))
text3 = models.CharField(max_length=1, verbose_name=_("Text 3"))
registration_date = models.DateTimeField(verbose_name=_("Registration Date"))
class Meta:
verbose_name = _("Registration")
verbose_name_plural = _("Registrations")
def __str__(self):
return f"{self.plate_number} - {self.text1} {self.text2} {self.text3}"
# TimestampedModel Abstract Class
class TimestampedModel(models.Model):
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
class Meta:
abstract = True
# 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"))
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"))
class Meta:
verbose_name = _("Dealer")
verbose_name_plural = _("Dealers")
def __str__(self):
return self.name
# 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"))
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"))
class Meta:
verbose_name = _("Vendor")
verbose_name_plural = _("Vendors")
def __str__(self):
return self.name
# Customer Model
class Customer(models.Model):
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"))
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"))
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
class Meta:
verbose_name = _("Customer")
verbose_name_plural = _("Customers")
def __str__(self):
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):
# if created:
# entity = EntityModel.objects.create(
# name=instance.name,
# admin=instance.user,
# address_1=instance.address,
# fy_start_month=1,
# accrual_method=True,
# depth=0,
# )
#
# default_coa = entity.create_chart_of_accounts(assign_as_default=True,
# commit=True,
# coa_name=_("Chart of Accounts"))
# if default_coa:
# entity.populate_default_coa(activate_accounts=True, coa_model=default_coa)
# print(f"Ledger entity created for Dealer: {instance.name}")
#
#
# # # Create Vendor
# @receiver(post_save, sender=Vendor)
# def create_ledger_vendor(sender, instance, created, **kwargs):
#
# if created:
# entity = EntityModel.objects.filter(name=instance.dealer.name).first()
#
# vendor = VendorModel.objects.create(
# entity_model=entity,
# vendor_name=instance.name,
# vendor_number=instance.crn,
# address_1=instance.address,
# phone=instance.phone_number,
# tax_id_number=instance.vrn,
# active=True,
# hidden=False,
# additional_info={
# "arabic_name": instance.arabic_name,
# "contact_person": instance.contact_person,
# },
# )
#
# print(f"VendorModel created for Vendor: {instance.name}")
#
#
# @receiver(post_save, sender=Customer)
# def create_customer(sender, instance, created, **kwargs):
#
# if created:
# entity = EntityModel.objects.filter(name=instance.dealer.name).first()
# name = f"{instance.first_name} {instance.middle_name} {instance.last_name}"
#
# customer = CustomerModel.objects.create(
# entity_model=entity,
# customer_name=name,
# customer_number=instance.national_id,
# address_1=instance.address,
# phone=instance.phone_number,
# email=instance.email,
# sales_tax_rate=0.15,
# )
#
# print(f"Customer created: {name}")
#
#
# # Create Item
# @receiver(post_save, sender=Car)
# def create_item_model(sender, instance, created, **kwargs):
# item_name = f"{instance.year} - {instance.id_car_make} - {instance.id_car_model} - {instance.id_car_trim}"
# uom_name = _("Car")
# unit_abbr = _("C")
#
# uom, uom_created = UnitOfMeasureModel.objects.get_or_create(
# name=uom_name,
# unit_abbr=unit_abbr
# )
#
# if uom_created:
# print(f"UOM created: {uom_name}")
# else:
# print(f"Using existing UOM: {uom_name}")
#
# entity = EntityModel.objects.filter(name=instance.dealer.name).first()
#
# inventory_account = AccountModel.objects.first()
# cogs_account = AccountModel.objects.first()
# earnings_account = AccountModel.objects.first()
#
# item = ItemModel.objects.create(
# entity=entity,
# uom=uom,
# name=item_name,
# item_role=ItemModelAbstract.ITEM_ROLE_INVENTORY,
# item_type=ItemModelAbstract.ITEM_TYPE_MATERIAL,
# item_id=instance.vin,
# sold_as_unit=True,
# inventory_received=1.00,
# inventory_received_value=0.00,
# inventory_account=inventory_account,
# for_inventory=True,
# is_product_or_service=True,
# cogs_account=cogs_account,
# earnings_account=earnings_account,
# is_active=True,
# additional_info={
# "remarks": instance.remarks,
# "status": instance.status,
# "stock_type": instance.stock_type,
# "mileage": instance.mileage,
# },
# )
#
# print(f"ItemModel {'created' if created else 'updated'} for Car: {item.name}")
#
#
# # update price - CarFinance
# @receiver(post_save, sender=CarFinance)
# def update_item_model_cost(sender, instance, created, **kwargs):
#
# ItemModel.objects.filter(item_id=instance.car.vin).update(
# inventory_received_value=instance.cost_price,
# default_amount=instance.cost_price,
# )
# print(f"Inventory item updated with CarFinance data for Car: {instance.car}")

257
inventory/services.py Normal file
View File

@ -0,0 +1,257 @@
"""
Services module
"""
import requests
import json
from .utils import get_jwt_token
from pyvin import VIN
from django.conf import settings
from openai import OpenAI
from .models import Car
def normalize_name(name):
return name.replace(' ', '').replace('-', '').lower()
def decode_vin_pyvin(vin):
vehicle = VIN(vin)
data = {
'Make': vehicle.Make,
'Model': vehicle.Model,
'ModelYear': vehicle.ModelYear,
}
print(data)
return data
# vehicle-info
# c2729afb
# 6d397471920412d672af1b8a02ca52ea
# option-info
# 367974ed
# 046b0412c1b4d3f8c39ec6375d6f3030
def elm(vin):
headers = {
"Content-Type": "application/json",
'app-id': 'c2729afb',
'app-key': '6d397471920412d672af1b8a02ca52ea',
'client-id': '94142c27-2536-47e9-8e28-9ca7728b9442',
}
url = 'https://vehicle-maintenance.api.elm.sa/api/v1/vehicles/vehicle-info?vin='+vin
payload = {}
response = requests.request("GET", url, headers=headers, data=payload)
car_info = json.loads(response.text)
return car_info
def get_unique_colors(api_response):
print(api_response)
colors = api_response.get("data", [])
print(colors)
unique_colors = {}
for color in colors:
color_name = color.get("name")
rgb = color.get("rgb")
if color_name not in unique_colors:
unique_colors[color_name] = rgb
return [{"name": name, "rgb": rgb} for name, rgb in unique_colors.items()]
def fetch_colors(car_data):
car_colors = {
"data": [
{"rgb": "192, 192, 192", "name": "Silver Metallic"},
{"rgb": "0, 0, 0", "name": "Jet Black"},
{"rgb": "255, 255, 255", "name": "Bright White"},
{"rgb": "128, 128, 128", "name": "Graphite Gray"},
{"rgb": "80, 80, 80", "name": "Gunmetal Gray"},
{"rgb": "255, 0, 0", "name": "Racing Red"},
{"rgb": "255, 69, 0", "name": "Inferno Orange"},
{"rgb": "0, 0, 255", "name": "Deep Blue Pearl"},
{"rgb": "75, 0, 130", "name": "Indigo Night"},
{"rgb": "255, 215, 0", "name": "Solar Gold"},
{"rgb": "34, 139, 34", "name": "Emerald Green"},
{"rgb": "60, 179, 113", "name": "Forest Mist Green"},
{"rgb": "255, 140, 0", "name": "Burnt Amber"},
{"rgb": "160, 82, 45", "name": "Copper Brown"},
{"rgb": "128, 0, 0", "name": "Crimson Maroon"},
{"rgb": "245, 245, 220", "name": "Beige Champagne"},
{"rgb": "169, 169, 169", "name": "Shadow Gray"},
{"rgb": "255, 250, 205", "name": "Lemon Pearl"},
{"rgb": "220, 220, 220", "name": "Platinum Silver"},
{"rgb": "105, 105, 105", "name": "Charcoal Metallic"},
{"rgb": "128, 0, 128", "name": "Royal Purple"},
{"rgb": "210, 105, 30", "name": "Sunset Bronze"},
{"rgb": "0, 128, 128", "name": "Teal Lagoon"},
{"rgb": "72, 61, 139", "name": "Midnight Blue"},
{"rgb": "255, 20, 147", "name": "Blazing Pink"},
{"rgb": "192, 57, 43", "name": "Crimson Flame"},
{"rgb": "255, 228, 196", "name": "Cream Sand"},
{"rgb": "112, 128, 144", "name": "Steel Gray"},
{"rgb": "0, 100, 0", "name": "Hunter Green"},
{"rgb": "255, 223, 0", "name": "Bright Yellow"},
{"rgb": "85, 107, 47", "name": "Olive Metallic"},
{"rgb": "128, 128, 0", "name": "Mustard Gold"},
{"rgb": "139, 69, 19", "name": "Cocoa Brown"},
{"rgb": "255, 165, 0", "name": "Tangerine Flame"},
{"rgb": "0, 0, 139", "name": "Navy Sapphire"},
{"rgb": "70, 130, 180", "name": "Skyline Blue"},
{"rgb": "220, 20, 60", "name": "Crimson Passion"},
{"rgb": "189, 183, 107", "name": "Khaki Dune"},
{"rgb": "50, 205, 50", "name": "Lime Essence"},
{"rgb": "139, 0, 139", "name": "Amethyst Glow"},
{"rgb": "255, 215, 180", "name": "Rosé Gold"},
{"rgb": "46, 139, 87", "name": "Moss Green"},
{"rgb": "72, 209, 204", "name": "Caribbean Aqua"},
{"rgb": "255, 240, 245", "name": "Pearl Blush"},
{"rgb": "244, 164, 96", "name": "Sierra Sunset"},
{"rgb": "139, 0, 0", "name": "Crimson Ruby"},
{"rgb": "192, 192, 192", "name": "Chrome"},
{"rgb": "255, 105, 180", "name": "Hot Magenta"},
{"rgb": "0, 255, 255", "name": "Ice Blue"},
{"rgb": "184, 134, 11", "name": "Golden Bronze"},
{"rgb": "128, 128, 64", "name": "Bronze Olive"},
{"rgb": "245, 222, 179", "name": "Wheat Cream"}
]
}
jwt_token = get_jwt_token()
if not jwt_token:
print("Failed to retrieve JWT token.")
return None
year = car_data['year']
make = car_data['make']
model = car_data['model']
url = "https://carapi.app/api/exterior-colors?year={}&make={}&model={}".format(year, make, model)
params = {
'limit': '1000',
'sort': 'name',
'direction': 'asc',
'verbose': 'no',
'all_trims': 'no',
}
headers = {
'Accept': 'application/json',
'Authorization': f'Bearer {jwt_token}',
}
try:
response = requests.get(url, headers=headers, params=params)
color_info = response.json()
if not color_info["data"] == []:
return get_unique_colors(color_info)
else:
return car_colors["data"]
except requests.exceptions.RequestException as e:
print(f"Error fetching color information: {e}")
return None
def fetch_interior_colors(car_data):
car_colors = {
"data": [
{"rgb": "0, 0, 0", "name": "Jet Black"},
{"rgb": "54, 69, 79", "name": "Charcoal Black"},
{"rgb": "255, 255, 255", "name": "Bright White"},
{"rgb": "245, 245, 220", "name": "Off-White"},
{"rgb": "210, 180, 140", "name": "Beige"},
{"rgb": "205, 133, 63", "name": "Tan"},
{"rgb": "128, 128, 128", "name": "Gray"},
{"rgb": "80, 80, 80", "name": "Graphite Gray"},
{"rgb": "112, 128, 144", "name": "Gunmetal Gray"},
{"rgb": "192, 192, 192", "name": "Silver Metallic"},
{"rgb": "139, 69, 19", "name": "Cognac Brown"},
{"rgb": "149, 94, 55", "name": "Chestnut Brown"},
{"rgb": "97, 63, 43", "name": "Espresso Brown"},
{"rgb": "72, 40, 34", "name": "Dark Chocolate"},
{"rgb": "139, 69, 19", "name": "Saddle Brown"},
{"rgb": "124, 79, 58", "name": "Mocha"},
{"rgb": "193, 154, 107", "name": "Camel Tan"},
{"rgb": "128, 0, 32", "name": "Burgundy"},
{"rgb": "128, 0, 0", "name": "Maroon"},
{"rgb": "139, 0, 0", "name": "Deep Red"},
{"rgb": "0, 0, 128", "name": "Navy Blue"},
{"rgb": "65, 105, 225", "name": "Royal Blue"},
{"rgb": "34, 139, 34", "name": "Forest Green"},
{"rgb": "80, 200, 120", "name": "Emerald Green"},
{"rgb": "255, 255, 240", "name": "Ivory"},
{"rgb": "242, 242, 242", "name": "Pearl White"},
{"rgb": "169, 169, 169", "name": "Stone Gray"},
{"rgb": "112, 128, 144", "name": "Slate Gray"},
{"rgb": "150, 111, 51", "name": "Ash Brown"},
{"rgb": "128, 128, 0", "name": "Olive Green"},
{"rgb": "25, 25, 112", "name": "Midnight Blue"},
{"rgb": "72, 60, 50", "name": "Taupe"}
]
}
jwt_token = get_jwt_token()
if not jwt_token:
print("Failed to retrieve JWT token.")
return None
year = car_data['year']
make = car_data['make']
model = car_data['model']
url = "https://carapi.app/api/interior-colors?year={}&make={}&model={}".format(year, make, model)
params = {
'limit': '100',
'sort': 'name',
'direction': 'asc',
'verbose': 'no',
'all_trims': 'no',
}
headers = {
'Accept': 'application/json',
'Authorization': f'Bearer {jwt_token}',
}
try:
response = requests.get(url, headers=headers, params=params)
color_info = response.json()
if not color_info["data"] == []:
return get_unique_colors(color_info)
else:
return car_colors["data"]
except requests.exceptions.RequestException as e:
print(f"Error fetching color information: {e}")
return None
def translate(content, *args, **kwargs):
client = OpenAI(api_key=settings.OPENAI_API_KEY)
completion = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "You are a translation assistant that translates English to Arabic."},
{"role": "user", "content": content}
],
temperature=0.3,
)
translation = completion.choices[0].message.content.strip()
return translation
def calculate_stock_value():
cars = Car.objects.all()
total_value = sum(car.selling_price for car in cars)
return total_value

Some files were not shown because too many files have changed in this diff Show More