update
This commit is contained in:
parent
68f7e3fb2c
commit
b22ef36524
3
.idea/car_inventory.iml
generated
3
.idea/car_inventory.iml
generated
@ -14,9 +14,10 @@
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.11 (car_inventory)" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="uv (car_inventory)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="jquery-3.5.1" level="application" />
|
||||
<orderEntry type="library" name="sweetalert2" level="application" />
|
||||
|
||||
5
.idea/misc.xml
generated
5
.idea/misc.xml
generated
@ -3,8 +3,5 @@
|
||||
<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" />
|
||||
<component name="PyPackaging">
|
||||
<option name="earlyReleasesAsUpgrades" value="true" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="uv (car_inventory)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
@ -1,31 +0,0 @@
|
||||
# Generated by Django 5.2.1 on 2025-05-25 23:01
|
||||
|
||||
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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,46 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-06-22 17:22
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
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='ChatLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user_message', models.TextField()),
|
||||
('chatbot_response', models.TextField()),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-timestamp'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AnalysisCache',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('prompt_hash', models.CharField(db_index=True, max_length=64)),
|
||||
('dealer_id', models.IntegerField(blank=True, db_index=True, null=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('expires_at', models.DateTimeField(db_index=True)),
|
||||
('result', models.JSONField()),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Analysis caches',
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -1,34 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-06-22 17:22
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('haikalbot', '0001_initial'),
|
||||
('inventory', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='chatlog',
|
||||
name='dealer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chatlogs', to='inventory.dealer'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='analysiscache',
|
||||
index=models.Index(fields=['prompt_hash', 'dealer_id'], name='haikalbot_a_prompt__b98e1e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='analysiscache',
|
||||
index=models.Index(fields=['expires_at'], name='haikalbot_a_expires_e790cd_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='chatlog',
|
||||
index=models.Index(fields=['dealer', 'timestamp'], name='haikalbot_c_dealer__6f8d63_idx'),
|
||||
),
|
||||
]
|
||||
@ -1,8 +1,5 @@
|
||||
from django.conf import settings
|
||||
|
||||
from inventory.utils import get_user_type
|
||||
|
||||
|
||||
def currency_context(request):
|
||||
"""
|
||||
Provides a context dictionary containing the currency setting. This is typically
|
||||
|
||||
@ -114,6 +114,7 @@ class InjectDealerMiddleware:
|
||||
# if request.user.is_authenticated and not request.session.get('otp_verified', False):
|
||||
# return redirect(reverse('verify_otp'))
|
||||
# return self.get_response(request)
|
||||
|
||||
class DealerSlugMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
@ -1,871 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-06-22 17:22
|
||||
|
||||
import datetime
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import inventory.mixins
|
||||
import inventory.models
|
||||
import phonenumber_field.modelfields
|
||||
import uuid
|
||||
from decimal import Decimal
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('appointment', '0001_initial'),
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CarEquipment',
|
||||
fields=[
|
||||
('id_car_equipment', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('year_begin', models.IntegerField(blank=True, null=True)),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Equipment',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarMake',
|
||||
fields=[
|
||||
('id_car_make', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')),
|
||||
('is_sa_import', models.BooleanField(default=False)),
|
||||
('car_type', models.SmallIntegerField(blank=True, choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')], null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Make',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
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')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Exterior Colors',
|
||||
'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')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Interior Colors',
|
||||
'verbose_name_plural': 'Interior Colors',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Payment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')),
|
||||
('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('sadad', 'SADAD')], max_length=50, verbose_name='method')),
|
||||
('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')),
|
||||
('payment_date', models.DateField(auto_now_add=True, verbose_name='date')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'payment',
|
||||
'verbose_name_plural': 'payments',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VatRate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AdditionalServices',
|
||||
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')),
|
||||
('description', models.TextField(verbose_name='Description')),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')),
|
||||
('taxable', models.BooleanField(default=False, verbose_name='taxable')),
|
||||
('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')),
|
||||
('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Additional Services',
|
||||
'verbose_name_plural': 'Additional Services',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Car',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='Primary Key')),
|
||||
('slug', models.SlugField(blank=True, help_text='Slug for the object. If not provided, it will be generated automatically.', null=True, unique=True, verbose_name='Slug')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
||||
('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'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], 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')),
|
||||
('hash', models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash')),
|
||||
('item_model', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='django_ledger.itemmodel', verbose_name='Item Model')),
|
||||
('id_car_make', 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')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Car',
|
||||
'verbose_name_plural': 'Cars',
|
||||
},
|
||||
),
|
||||
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')),
|
||||
('selling_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Selling Price')),
|
||||
('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')),
|
||||
('is_sold', models.BooleanField(default=False)),
|
||||
('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices')),
|
||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Car Financial Details',
|
||||
'verbose_name_plural': 'Car Financial Details',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarModel',
|
||||
fields=[
|
||||
('id_car_model', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||
('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='CarOption',
|
||||
fields=[
|
||||
('id_car_option', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||
('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Option',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarOptionValue',
|
||||
fields=[
|
||||
('id_car_option_value', models.AutoField(primary_key=True, serialize=False)),
|
||||
('value', models.CharField(max_length=500)),
|
||||
('unit', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('is_base', models.IntegerField()),
|
||||
('id_car_equipment', models.ForeignKey(db_column='id_car_equipment', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carequipment')),
|
||||
('id_car_option', models.ForeignKey(db_column='id_car_option', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Option Value',
|
||||
},
|
||||
),
|
||||
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(blank=True, max_length=1, null=True, verbose_name='Text 2')),
|
||||
('text3', models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 3')),
|
||||
('registration_date', models.DateTimeField(verbose_name='Registration Date')),
|
||||
('car', models.OneToOneField(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(blank=True, max_length=255, null=True)),
|
||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('year_begin', models.IntegerField(blank=True, null=True)),
|
||||
('year_end', models.IntegerField(blank=True, null=True)),
|
||||
('generation_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||
('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)),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||
('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(blank=True, max_length=255, null=True)),
|
||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('start_production_year', models.IntegerField(blank=True, null=True)),
|
||||
('end_production_year', models.IntegerField(blank=True, null=True)),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=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='carequipment',
|
||||
name='id_car_trim',
|
||||
field=models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim'),
|
||||
),
|
||||
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.DateField(verbose_name='Custom Date')),
|
||||
('car', models.OneToOneField(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(blank=True, max_length=10, null=True, verbose_name='Commercial Registration Number')),
|
||||
('vrn', models.CharField(blank=True, max_length=15, null=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')),
|
||||
('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')),
|
||||
('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||
('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel')),
|
||||
('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),
|
||||
managers=[
|
||||
('objects', inventory.models.DealerUserManager()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomGroup',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='Group')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Customer',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], default='na', max_length=10, verbose_name='Title')),
|
||||
('first_name', models.CharField(max_length=50, verbose_name='First Name')),
|
||||
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
|
||||
('gender', models.CharField(choices=[('m', 'Male'), ('f', 'Female')], max_length=1, verbose_name='Gender')),
|
||||
('dob', models.DateField(blank=True, null=True, verbose_name='Date of Birth')),
|
||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
|
||||
('national_id', models.CharField(blank=True, max_length=10, null=True, 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')),
|
||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='customers/', verbose_name='Image')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('slug', models.SlugField(blank=True, editable=False, max_length=255, null=True, unique=True)),
|
||||
('customer_model', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.customermodel')),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='customer_profile', to=settings.AUTH_USER_MODEL)),
|
||||
('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.CreateModel(
|
||||
name='CarTransfer',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')),
|
||||
('quantity', models.IntegerField(default=1, verbose_name='Quantity')),
|
||||
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
|
||||
('status', models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accepted', 'Accepted'), ('success', 'Success'), ('reject', 'Reject'), ('cancelled', 'Cancelled')])),
|
||||
('is_approved', models.BooleanField(default=False)),
|
||||
('active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
||||
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_logs', to='inventory.car', verbose_name='Car')),
|
||||
('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')),
|
||||
('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Car Transfer Log',
|
||||
'verbose_name_plural': 'Car Transfer Logs',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarLocation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('description', models.TextField(blank=True, help_text='Optional description about the showroom placement.', null=True, verbose_name='Description')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Updated')),
|
||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location', to='inventory.car', verbose_name='Car')),
|
||||
('owner', models.ForeignKey(help_text='Dealer who owns the car.', on_delete=django.db.models.deletion.CASCADE, related_name='owned_cars', to='inventory.dealer', verbose_name='Owner')),
|
||||
('showroom', models.ForeignKey(help_text='Dealer where the car is displayed (can be the owner).', on_delete=django.db.models.deletion.CASCADE, related_name='showroom_cars', to='inventory.dealer', verbose_name='Showroom')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Car Location',
|
||||
'verbose_name_plural': 'Car Locations',
|
||||
},
|
||||
),
|
||||
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.AddField(
|
||||
model_name='additionalservices',
|
||||
name='dealer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Activity',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('activity_type', models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('meeting', 'Meeting'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('negotiation', 'Negotiation'), ('follow_up', 'Follow Up'), ('won', 'Won'), ('lost', 'Lost'), ('closed', 'Closed'), ('converted', 'Converted'), ('transfer', 'Transfer'), ('add_car', 'Add Car'), ('sale_car', 'Sale Car'), ('reserve_car', 'Reserve Car'), ('transfer_car', 'Transfer Car'), ('remove_car', 'Remove Car'), ('create_quotation', 'Create Quotation'), ('cancel_quotation', 'Cancel Quotation'), ('create_order', 'Create Order'), ('cancel_order', 'Cancel Order'), ('create_invoice', 'Create Invoice'), ('cancel_invoice', 'Cancel Invoice')], max_length=50, verbose_name='Activity Type')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.contenttype')),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created_by', to=settings.AUTH_USER_MODEL)),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='inventory.dealer')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Activity',
|
||||
'verbose_name_plural': 'Activities',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DealerSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('additional_info', models.JSONField(blank=True, default=dict, null=True)),
|
||||
('bill_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_cash', to='django_ledger.accountmodel')),
|
||||
('bill_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to='django_ledger.accountmodel')),
|
||||
('bill_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to='django_ledger.accountmodel')),
|
||||
('dealer', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='inventory.dealer')),
|
||||
('invoice_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_cash', to='django_ledger.accountmodel')),
|
||||
('invoice_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to='django_ledger.accountmodel')),
|
||||
('invoice_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to='django_ledger.accountmodel')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Email',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.UUIDField()),
|
||||
('from_email', models.TextField(blank=True, null=True, verbose_name='From Email')),
|
||||
('to_email', models.TextField(blank=True, null=True, verbose_name='To Email')),
|
||||
('subject', models.TextField(blank=True, null=True, verbose_name='Subject')),
|
||||
('message', models.TextField(blank=True, null=True, verbose_name='Message')),
|
||||
('status', models.CharField(choices=[('SENT', 'Sent'), ('FAILED', 'Failed'), ('DELIVERED', 'Delivered'), ('OPEN', 'Open'), ('DRAFT', 'Draft')], default='OPEN', max_length=20, verbose_name='Status')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='emails_created', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Email',
|
||||
'verbose_name_plural': 'Emails',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Lead',
|
||||
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')),
|
||||
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
|
||||
('email', models.EmailField(max_length=254, verbose_name='Email')),
|
||||
('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')),
|
||||
('lead_type', models.CharField(choices=[('customer', 'Customer'), ('organization', 'Organization')], default='customer', max_length=50, verbose_name='Lead Type')),
|
||||
('source', models.CharField(choices=[('referrals', 'Referrals'), ('whatsapp', 'WhatsApp'), ('showroom', 'Showroom'), ('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('x', 'X'), ('facebook', 'Facebook'), ('motory', 'Motory'), ('influencers', 'Influencers'), ('youtube', 'Youtube'), ('campaign', 'Campaign')], max_length=50, verbose_name='Source')),
|
||||
('channel', models.CharField(choices=[('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('website', 'Website'), ('email', 'Email'), ('form', 'Form')], max_length=50, verbose_name='Channel')),
|
||||
('status', models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], db_index=True, default='new', max_length=50, verbose_name='Status')),
|
||||
('next_action', models.CharField(blank=True, max_length=255, null=True, verbose_name='Next Action')),
|
||||
('next_action_date', models.DateTimeField(blank=True, null=True, verbose_name='Next Action Date')),
|
||||
('is_converted', models.BooleanField(default=False)),
|
||||
('converted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('slug', models.SlugField(blank=True, null=True, unique=True)),
|
||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='customer_leads', to='inventory.customer')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer')),
|
||||
('id_car_make', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')),
|
||||
('id_car_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Lead',
|
||||
'verbose_name_plural': 'Leads',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Notes',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.UUIDField()),
|
||||
('note', models.TextField(verbose_name='Note')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='inventory.dealer')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Note',
|
||||
'verbose_name_plural': 'Notes',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Notification',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('message', models.CharField(max_length=255, verbose_name='Message')),
|
||||
('is_read', models.BooleanField(default=False, verbose_name='Is Read')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Notification',
|
||||
'verbose_name_plural': 'Notifications',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Organization',
|
||||
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')),
|
||||
('crn', models.CharField(max_length=15, verbose_name='Commercial Registration Number')),
|
||||
('vrn', models.CharField(max_length=15, verbose_name='VAT Registration Number')),
|
||||
('email', models.EmailField(max_length=254, verbose_name='Email')),
|
||||
('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', verbose_name='Logo')),
|
||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('slug', models.SlugField(blank=True, editable=False, max_length=255, null=True, unique=True)),
|
||||
('customer_model', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.customermodel')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='organization_profile', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Organization',
|
||||
'verbose_name_plural': 'Organizations',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Opportunity',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('crn', models.CharField(blank=True, max_length=20, null=True, verbose_name='CRN')),
|
||||
('vrn', models.CharField(blank=True, max_length=20, null=True, verbose_name='VRN')),
|
||||
('salary', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Salary')),
|
||||
('priority', models.CharField(choices=[('high', 'High'), ('medium', 'Medium'), ('low', 'Low')], default='medium', max_length=20, verbose_name='Priority')),
|
||||
('stage', models.CharField(choices=[('qualification', 'Qualification'), ('test_drive', 'Test Drive'), ('quotation', 'Quotation'), ('negotiation', 'Negotiation'), ('financing', 'Financing'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost'), ('on_hold', 'On Hold')], max_length=20, verbose_name='Stage')),
|
||||
('probability', models.PositiveIntegerField(validators=[inventory.models.validate_probability])),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Amount')),
|
||||
('expected_revenue', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Expected Revenue')),
|
||||
('vehicle_of_interest_make', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('vehicle_of_interest_model', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('expected_close_date', models.DateField(blank=True, null=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('slug', models.SlugField(blank=True, help_text='Unique slug for the opportunity.', null=True, unique=True, verbose_name='Slug')),
|
||||
('loss_reason', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')),
|
||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer')),
|
||||
('estimate', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to='django_ledger.estimatemodel')),
|
||||
('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead')),
|
||||
('organization', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.organization', verbose_name='Organization')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Opportunity',
|
||||
'verbose_name_plural': 'Opportunities',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lead',
|
||||
name='organization',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='organization_leads', to='inventory.organization'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Refund',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')),
|
||||
('reason', models.TextField(blank=True, verbose_name='reason')),
|
||||
('refund_date', models.DateField(auto_now_add=True, verbose_name='refund date')),
|
||||
('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='refund', to='inventory.payment')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'refund',
|
||||
'verbose_name_plural': 'refunds',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Representative',
|
||||
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')),
|
||||
('id_number', models.CharField(max_length=10, unique=True, verbose_name='ID Number')),
|
||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||
('email', models.EmailField(max_length=255, verbose_name='Email Address')),
|
||||
('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='representatives', to='inventory.dealer')),
|
||||
('organization', models.ManyToManyField(related_name='representatives', to='inventory.organization')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Representative',
|
||||
'verbose_name_plural': 'Representatives',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SaleOrder',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('comments', models.TextField(blank=True, null=True)),
|
||||
('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)),
|
||||
('status', models.CharField(choices=[('PENDING_APPROVAL', 'Pending Approval'), ('APPROVED', 'Approved'), ('IN_FINANCING', 'In Financing'), ('PARTIALLY_PAID', 'Partially Paid'), ('FULLY_PAID', 'Fully Paid'), ('PENDING_DELIVERY', 'Pending Delivery'), ('DELIVERED', 'Delivered'), ('CANCELLED', 'Cancelled')], default='PENDING_APPROVAL', help_text='Current status of the sales order.', max_length=20)),
|
||||
('order_date', models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time the sales order was created.')),
|
||||
('expected_delivery_date', models.DateField(blank=True, help_text='The planned date for vehicle delivery.', null=True)),
|
||||
('actual_delivery_date', models.DateTimeField(blank=True, help_text='The actual date and time the vehicle was delivered.', null=True)),
|
||||
('cancelled_date', models.DateTimeField(blank=True, help_text='The date and time the order was cancelled, if applicable.', null=True)),
|
||||
('cancellation_reason', models.TextField(blank=True, help_text='Reason for cancellation, if applicable.', null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.ForeignKey(blank=True, help_text='The user who created this sales order.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_sales_orders', to=settings.AUTH_USER_MODEL)),
|
||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='inventory.customer', verbose_name='Customer')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='inventory.dealer')),
|
||||
('estimate', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.estimatemodel', verbose_name='Estimate')),
|
||||
('invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.invoicemodel', verbose_name='Invoice')),
|
||||
('last_modified_by', models.ForeignKey(blank=True, help_text='The user who last modified this sales order.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modified_sales_orders', to=settings.AUTH_USER_MODEL)),
|
||||
('opportunity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='inventory.opportunity', verbose_name='Opportunity')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Sales Order',
|
||||
'verbose_name_plural': 'Sales Orders',
|
||||
'ordering': ['-order_date'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Schedule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('purpose', models.CharField(choices=[('product_demo', 'Product Demo'), ('follow_up_call', 'Follow-Up Call'), ('contract_discussion', 'Contract Discussion'), ('sales_meeting', 'Sales Meeting'), ('support_call', 'Support Call'), ('other', 'Other')], max_length=200)),
|
||||
('scheduled_at', models.DateTimeField()),
|
||||
('scheduled_type', models.CharField(choices=[('call', 'Call'), ('meeting', 'Meeting'), ('email', 'Email')], default='Call', max_length=200)),
|
||||
('duration', models.DurationField(default=datetime.timedelta(seconds=300))),
|
||||
('notes', models.TextField(blank=True, null=True)),
|
||||
('status', models.CharField(choices=[('scheduled', 'Scheduled'), ('completed', 'Completed'), ('canceled', 'Canceled')], default='Scheduled', max_length=200)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='django_ledger.customermodel')),
|
||||
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='inventory.lead')),
|
||||
('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-scheduled_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Staff',
|
||||
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')),
|
||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||
('staff_type', models.CharField(choices=[('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type')),
|
||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('slug', models.SlugField(blank=True, editable=False, max_length=255, null=True, unique=True)),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='inventory.dealer')),
|
||||
('staff_member', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='appointment.staffmember')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Staff',
|
||||
'verbose_name_plural': 'Staff',
|
||||
'permissions': [],
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
managers=[
|
||||
('objects', inventory.models.StaffUserManager()),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='opportunity',
|
||||
name='staff',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LeadStatusHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('old_status', models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], max_length=50, verbose_name='Old Status')),
|
||||
('new_status', models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], max_length=50, verbose_name='New Status')),
|
||||
('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')),
|
||||
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')),
|
||||
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Lead Status History',
|
||||
'verbose_name_plural': 'Lead Status Histories',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lead',
|
||||
name='staff',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tasks',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.UUIDField()),
|
||||
('title', models.CharField(max_length=255, verbose_name='Title')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Description')),
|
||||
('due_date', models.DateField(verbose_name='Due Date')),
|
||||
('completed', models.BooleanField(default=False, verbose_name='Completed')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('assigned_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='tasks_assigned', to=settings.AUTH_USER_MODEL)),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='tasks_created', to=settings.AUTH_USER_MODEL)),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='inventory.dealer')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Task',
|
||||
'verbose_name_plural': 'Tasks',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserActivityLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('action', models.TextField()),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'User Activity Log',
|
||||
'verbose_name_plural': 'User Activity Logs',
|
||||
'ordering': ['-timestamp'],
|
||||
},
|
||||
),
|
||||
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')),
|
||||
('email', models.EmailField(max_length=255, verbose_name='Email Address')),
|
||||
('address', models.CharField(max_length=200, verbose_name='Address')),
|
||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')),
|
||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True, verbose_name='Slug')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')),
|
||||
('vendor_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='django_ledger.vendormodel', verbose_name='Vendor Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Vendor',
|
||||
'verbose_name_plural': 'Vendors',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='car',
|
||||
name='vendor',
|
||||
field=models.ForeignKey(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, verbose_name='Reserved At')),
|
||||
('reserved_until', models.DateTimeField(verbose_name='Reserved Until')),
|
||||
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.car', verbose_name='Car')),
|
||||
('reserved_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to=settings.AUTH_USER_MODEL, verbose_name='Reserved By')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Car Reservation',
|
||||
'verbose_name_plural': 'Car Reservations',
|
||||
'ordering': ['-reserved_at'],
|
||||
'unique_together': {('car', 'reserved_until')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DealersMake',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('added_at', models.DateTimeField(auto_now_add=True)),
|
||||
('car_make', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='car_dealers', to='inventory.carmake')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dealer_makes', to='inventory.dealer')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('dealer', 'car_make')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.car')),
|
||||
('exterior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.exteriorcolors')),
|
||||
('interior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.interiorcolors')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Color',
|
||||
'verbose_name_plural': 'Colors',
|
||||
'unique_together': {('car', 'exterior', 'interior')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user_data', models.JSONField(blank=True, null=True)),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0.01)])),
|
||||
('currency', models.CharField(default='SAR', max_length=3)),
|
||||
('payment_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('status', models.CharField(choices=[('initiated', 'initiated'), ('pending', 'Pending'), ('completed', 'Completed'), ('paid', 'Paid'), ('failed', 'Failed'), ('refunded', 'Refunded'), ('cancelled', 'Cancelled')], default='pending', max_length=10)),
|
||||
('payment_method', models.CharField(choices=[('credit_card', 'Credit Card'), ('debit_card', 'Debit Card'), ('paypal', 'PayPal'), ('bank_transfer', 'Bank Transfer'), ('crypto', 'Cryptocurrency'), ('other', 'Other')], max_length=20)),
|
||||
('transaction_id', models.CharField(blank=True, max_length=100, null=True, unique=True)),
|
||||
('invoice_number', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('order_reference', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('gateway_response', models.JSONField(blank=True, null=True)),
|
||||
('gateway_name', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('is_recurring', models.BooleanField(default=False)),
|
||||
('billing_email', models.EmailField(blank=True, max_length=254, null=True)),
|
||||
('billing_address', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Payment History',
|
||||
'verbose_name_plural': 'Payment Histories',
|
||||
'ordering': ['-payment_date'],
|
||||
'indexes': [models.Index(fields=['transaction_id'], name='inventory_p_transac_9469f3_idx'), models.Index(fields=['user'], name='inventory_p_user_id_c31626_idx'), models.Index(fields=['status'], name='inventory_p_status_abcb77_idx'), models.Index(fields=['payment_date'], name='inventory_p_payment_b3068c_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -41,7 +41,7 @@ urlpatterns = [
|
||||
path("dashboards/sales/", views.SalesDashboard.as_view(), name="sales_dashboard"),
|
||||
path("test/", views.TestView.as_view(), name="test"),
|
||||
path("cars/inventory/table/", views.CarListViewTable.as_view(), name="car_table"),
|
||||
path("export/format/", TableExport, name="export"),
|
||||
path("export/format/", TableExport.export, name="export"),
|
||||
# Dealer URLs
|
||||
path(
|
||||
"<slug:slug>/dealers/", views.DealerDetailView.as_view(), name="dealer_detail"
|
||||
@ -440,63 +440,23 @@ urlpatterns = [
|
||||
path("<slug:dealer_slug>/user/create/", views.UserCreateView.as_view(), name="user_create"),
|
||||
path("<slug:dealer_slug>/user/<slug:slug>/", views.UserDetailView.as_view(), name="user_detail"),
|
||||
path("<slug:dealer_slug>/user/<slug:slug>/groups/", views.UserGroupView, name="user_groups"),
|
||||
path(
|
||||
"<slug:dealer_slug>/user/<slug:slug>/update/", views.UserUpdateView.as_view(), name="user_update"
|
||||
),
|
||||
path("<slug:dealer_slug>/user/<slug:slug>/update/", views.UserUpdateView.as_view(), name="user_update"),
|
||||
path("<slug:dealer_slug>/user/<slug:slug>/confirm/", views.UserDeleteview, name="user_delete"),
|
||||
# Group URLs
|
||||
path("<slug:dealer_slug>/group/create/", views.GroupCreateView.as_view(), name="group_create"),
|
||||
path(
|
||||
"<slug:dealer_slug>/group/<int:pk>/update/", views.GroupUpdateView.as_view(), name="group_update"
|
||||
),
|
||||
path("<slug:dealer_slug>/group/<int:pk>/update/", views.GroupUpdateView.as_view(), name="group_update"),
|
||||
path("<slug:dealer_slug>/group/<int:pk>/", views.GroupDetailView.as_view(), name="group_detail"),
|
||||
path("<slug:dealer_slug>/group/", views.GroupListView.as_view(), name="group_list"),
|
||||
path("<slug:dealer_slug>/group/<int:pk>/confirm/", views.GroupDeleteview, name="group_delete"),
|
||||
path(
|
||||
"<slug:dealer_slug>/group/<int:pk>/permission/", views.GroupPermissionView, name="group_permission"
|
||||
),
|
||||
# Organization URLs
|
||||
path(
|
||||
"<slug:dealer_slug>/organizations/create/",
|
||||
views.OrganizationCreateView.as_view(),
|
||||
name="organization_create",
|
||||
),
|
||||
path(
|
||||
"<slug:dealer_slug>/organizations/", views.OrganizationListView.as_view(), name="organization_list"
|
||||
),
|
||||
path(
|
||||
"<slug:dealer_slug>/organizations/<slug:slug>/",
|
||||
views.OrganizationDetailView.as_view(),
|
||||
name="organization_detail",
|
||||
),
|
||||
path(
|
||||
"<slug:dealer_slug>/organizations/<slug:slug>/update/",
|
||||
views.OrganizationUpdateView.as_view(),
|
||||
name="organization_update",
|
||||
),
|
||||
path(
|
||||
"<slug:dealer_slug>/organizations/<slug:slug>/delete/",
|
||||
views.OrganizationDeleteView,
|
||||
name="organization_delete",
|
||||
),
|
||||
# Representative URLs
|
||||
path(
|
||||
"representatives/",
|
||||
views.RepresentativeListView.as_view(),
|
||||
name="representative_list",
|
||||
),
|
||||
path(
|
||||
"representatives/<int:pk>/",
|
||||
views.RepresentativeDetailView.as_view(),
|
||||
name="representative_detail",
|
||||
),
|
||||
path(
|
||||
"representatives/create/",
|
||||
views.RepresentativeCreateView.as_view(),
|
||||
name="representative_create",
|
||||
),
|
||||
path(
|
||||
"representatives/<int:pk>/update/",
|
||||
path("<slug:dealer_slug>/group/<int:pk>/permission/", views.GroupPermissionView, name="group_permission"),
|
||||
path("<slug:dealer_slug>/organizations/create/", views.OrganizationCreateView.as_view(), name="organization_create"),
|
||||
path("<slug:dealer_slug>/organizations/", views.OrganizationListView.as_view(), name="organization_list"),
|
||||
path("<slug:dealer_slug>/organizations/<slug:slug>/", views.OrganizationDetailView.as_view(), name="organization_detail"),
|
||||
path("<slug:dealer_slug>/organizations/<slug:slug>/update/", views.OrganizationUpdateView.as_view(), name="organization_update"),
|
||||
path("<slug:dealer_slug>/organizations/<slug:slug>/delete/", views.OrganizationDeleteView, name="organization_delete"),
|
||||
path("representatives/", views.RepresentativeListView.as_view(), name="representative_list"),
|
||||
path("representatives/<int:pk>/", views.RepresentativeDetailView.as_view(), name="representative_detail"),
|
||||
path("representatives/create/", views.RepresentativeCreateView.as_view(),name="representative_create"),
|
||||
path("representatives/<int:pk>/update/",
|
||||
views.RepresentativeUpdateView.as_view(),
|
||||
name="representative_update",
|
||||
),
|
||||
@ -505,9 +465,6 @@ urlpatterns = [
|
||||
views.RepresentativeDeleteView.as_view(),
|
||||
name="representative_delete",
|
||||
),
|
||||
#####################################################################
|
||||
# Ledger
|
||||
#####################################################################
|
||||
path("<slug:dealer_slug>/ledgers/", views.LedgerModelListView.as_view(), name="ledger_list"),
|
||||
path(
|
||||
"<slug:dealer_slug>/ledgers/create/", views.LedgerModelCreateView.as_view(), name="ledger_create"
|
||||
@ -613,9 +570,6 @@ urlpatterns = [
|
||||
views.LedgerModelDeleteView.as_view(),
|
||||
name="ledger-delete",
|
||||
),
|
||||
##############################################################
|
||||
# Bank Account
|
||||
##############################################################
|
||||
path(
|
||||
"<slug:dealer_slug>/bank_accounts/",
|
||||
views.BankAccountListView.as_view(),
|
||||
@ -641,7 +595,6 @@ urlpatterns = [
|
||||
views.bank_account_delete,
|
||||
name="bank_account_delete",
|
||||
),
|
||||
# Account
|
||||
path(
|
||||
"<slug:dealer_slug>/coa_accounts/",
|
||||
views.AccountListView.as_view(),
|
||||
|
||||
@ -624,8 +624,9 @@ class CarCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
dealer = get_user_type(self.request)
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["vendor_exists"] = self.request.dealer.vendors.exists()
|
||||
context["vendor_exists"] = dealer.vendors.exists()
|
||||
return context
|
||||
|
||||
|
||||
|
||||
357
pyproject.toml
Normal file
357
pyproject.toml
Normal file
@ -0,0 +1,357 @@
|
||||
[project]
|
||||
name = "car-inventory"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"aiohappyeyeballs>=2.6.1",
|
||||
"aiohttp>=3.12.13",
|
||||
"aiohttp-retry>=2.9.1",
|
||||
"aiosignal>=1.3.2",
|
||||
"alabaster>=1.0.0",
|
||||
"albucore>=0.0.24",
|
||||
"albumentations>=2.0.8",
|
||||
"annotated-types>=0.7.0",
|
||||
"anthropic>=0.55.0",
|
||||
"anyio>=4.9.0",
|
||||
"arabic-reshaper>=3.0.0",
|
||||
"argcomplete>=3.6.2",
|
||||
"arrow>=1.3.0",
|
||||
"asgiref>=3.8.1",
|
||||
"astor>=0.8.1",
|
||||
"astroid>=3.3.10",
|
||||
"attrs>=25.3.0",
|
||||
"autopep8>=2.3.2",
|
||||
"babel>=2.17.0",
|
||||
"beautifulsoup4>=4.13.4",
|
||||
"bleach>=6.2.0",
|
||||
"blessed>=1.21.0",
|
||||
"blinker>=1.9.0",
|
||||
"boto3>=1.38.44",
|
||||
"botocore>=1.38.44",
|
||||
"brotli>=1.1.0",
|
||||
"cachetools>=5.5.2",
|
||||
"cattrs>=25.1.1",
|
||||
"certifi>=2025.6.15",
|
||||
"cffi>=1.17.1",
|
||||
"chardet>=5.2.0",
|
||||
"charset-normalizer>=3.4.2",
|
||||
"click>=7.1.2",
|
||||
"cohere>=5.15.0",
|
||||
"colorama>=0.4.6",
|
||||
"commonmark>=0.9.1",
|
||||
"contourpy>=1.3.2",
|
||||
"crispy-bootstrap5>=2025.6",
|
||||
"cryptography>=45.0.4",
|
||||
"cssselect2>=0.8.0",
|
||||
"ctranslate2>=4.6.0",
|
||||
"cycler>=0.12.1",
|
||||
"cython>=3.1.2",
|
||||
"dataclasses-json>=0.6.7",
|
||||
"decorator>=5.2.1",
|
||||
"defusedxml>=0.7.1",
|
||||
"desert>=2020.11.18",
|
||||
"diff-match-patch>=20241021",
|
||||
"dill>=0.4.0",
|
||||
"distro>=1.9.0",
|
||||
"dj-rest-auth>=7.0.1",
|
||||
"django>=5.2.3",
|
||||
"django-allauth>=65.9.0",
|
||||
"django-appointment>=3.6.0",
|
||||
"django-autoslug>=1.9.9",
|
||||
"django-background-tasks>=1.2.8",
|
||||
"django-bootstrap5>=25.1",
|
||||
"django-ckeditor>=6.7.3",
|
||||
"django-classy-tags>=4.1.0",
|
||||
"django-cors-headers>=4.7.0",
|
||||
"django-countries>=7.6.1",
|
||||
"django-crispy-forms>=2.4",
|
||||
"django-debug-toolbar>=5.2.0",
|
||||
"django-easy-audit>=1.3.7",
|
||||
"django-extensions>=4.1",
|
||||
"django-filter>=25.1",
|
||||
"django-formtools>=2.5.1",
|
||||
"django-import-export>=4.3.8",
|
||||
"django-js-asset>=3.1.2",
|
||||
"django-ledger==0.7.8",
|
||||
"django-model-utils>=5.0.0",
|
||||
"django-money>=3.5.4",
|
||||
"django-next-url-mixin>=0.4.0",
|
||||
"django-nine>=0.2.7",
|
||||
"django-nonefield>=0.4",
|
||||
"django-ordered-model>=3.7.4",
|
||||
"django-pdf-actions>=0.1.52",
|
||||
"django-phonenumber-field>=8.1.0",
|
||||
"django-picklefield>=3.3",
|
||||
"django-plans>=2.0.0",
|
||||
"django-prometheus>=2.4.1",
|
||||
"django-q2>=1.8.0",
|
||||
"django-schema-graph>=3.1.0",
|
||||
"django-sekizai>=4.1.0",
|
||||
"django-sequences>=3.0",
|
||||
"django-silk>=5.4.0",
|
||||
"django-simple-history>=3.10.1",
|
||||
"django-sms>=0.7.0",
|
||||
"django-sslserver-v2>=1.0",
|
||||
"django-tables2>=2.7.5",
|
||||
"django-treebeard>=4.7.1",
|
||||
"django-view-breadcrumbs>=2.5.1",
|
||||
"django-widget-tweaks>=1.5.0",
|
||||
"djangocms-admin-style>=3.3.1",
|
||||
"djangorestframework>=3.16.0",
|
||||
"djangorestframework-simplejwt>=5.5.0",
|
||||
"djangoviz>=0.1.1",
|
||||
"djhtml>=3.0.8",
|
||||
"docopt>=0.6.2",
|
||||
"docutils>=0.21.2",
|
||||
"easy-thumbnails>=2.10",
|
||||
"emoji>=2.14.1",
|
||||
"et-xmlfile>=2.0.0",
|
||||
"eval-type-backport>=0.2.2",
|
||||
"executing>=2.2.0",
|
||||
"faker>=37.4.0",
|
||||
"fasta2a>=0.3.4",
|
||||
"fastavro>=1.11.1",
|
||||
"filelock>=3.18.0",
|
||||
"fire>=0.7.0",
|
||||
"fonttools>=4.58.4",
|
||||
"fpdf>=1.7.2",
|
||||
"fpdf2>=2.8.3",
|
||||
"frozenlist>=1.7.0",
|
||||
"fsspec>=2025.5.1",
|
||||
"google-auth>=2.40.3",
|
||||
"google-genai>=1.22.0",
|
||||
"googleapis-common-protos>=1.70.0",
|
||||
"gprof2dot>=2025.4.14",
|
||||
"graphqlclient>=0.2.4",
|
||||
"greenlet>=3.2.3",
|
||||
"griffe>=1.7.3",
|
||||
"groq>=0.29.0",
|
||||
"h11>=0.16.0",
|
||||
"h2>=4.2.0",
|
||||
"hf-xet>=1.1.5",
|
||||
"hpack>=4.1.0",
|
||||
"hstspreload>=2025.1.1",
|
||||
"httpcore>=1.0.9",
|
||||
"httpx>=0.28.1",
|
||||
"httpx-sse>=0.4.0",
|
||||
"huggingface-hub>=0.33.1",
|
||||
"hyperframe>=6.1.0",
|
||||
"icalendar>=6.3.1",
|
||||
"idna>=3.10",
|
||||
"imageio>=2.37.0",
|
||||
"imagesize>=1.4.1",
|
||||
"imgaug>=0.4.0",
|
||||
"importlib-metadata>=8.7.0",
|
||||
"iso4217>=1.14.20250512",
|
||||
"isodate>=0.7.2",
|
||||
"isort>=6.0.1",
|
||||
"itsdangerous>=2.2.0",
|
||||
"jinja2>=3.1.6",
|
||||
"jiter>=0.10.0",
|
||||
"jmespath>=1.0.1",
|
||||
"joblib>=1.5.1",
|
||||
"jsonpatch>=1.33",
|
||||
"jsonpointer>=3.0.0",
|
||||
"jwt>=1.4.0",
|
||||
"kiwisolver>=1.4.8",
|
||||
"langchain>=0.3.26",
|
||||
"langchain-community>=0.3.26",
|
||||
"langchain-core>=0.3.66",
|
||||
"langchain-ollama>=0.3.3",
|
||||
"langchain-text-splitters>=0.3.8",
|
||||
"langsmith>=0.4.2",
|
||||
"lazy-loader>=0.4",
|
||||
"ledger>=1.0.1",
|
||||
"libretranslatepy>=2.1.4",
|
||||
"lmdb>=1.6.2",
|
||||
"logfire>=3.21.1",
|
||||
"logfire-api>=3.21.1",
|
||||
"luhnchecker>=0.0.12",
|
||||
"lxml>=5.4.0",
|
||||
"markdown>=3.8.2",
|
||||
"markdown-it-py>=3.0.0",
|
||||
"markupsafe>=3.0.2",
|
||||
"marshmallow>=3.26.1",
|
||||
"matplotlib>=3.10.3",
|
||||
"mccabe>=0.7.0",
|
||||
"mcp>=1.9.4",
|
||||
"mdurl>=0.1.2",
|
||||
"mistralai>=1.8.2",
|
||||
"mouseinfo>=0.1.3",
|
||||
"mpmath>=1.3.0",
|
||||
"multidict>=6.5.1",
|
||||
"mypy-extensions>=1.1.0",
|
||||
"networkx>=3.5",
|
||||
"newrelic>=10.14.0",
|
||||
"nltk>=3.9.1",
|
||||
"num2words>=0.5.14",
|
||||
"numpy>=2.3.1",
|
||||
"oauthlib>=3.3.1",
|
||||
"ofxtools>=0.9.5",
|
||||
"ollama>=0.5.1",
|
||||
"openai>=1.91.0",
|
||||
"opencv-contrib-python>=4.11.0.86",
|
||||
"opencv-python>=4.11.0.86",
|
||||
"opencv-python-headless>=4.11.0.86",
|
||||
"openpyxl>=3.1.5",
|
||||
"opentelemetry-api>=1.34.1",
|
||||
"opentelemetry-exporter-otlp-proto-common>=1.34.1",
|
||||
"opentelemetry-exporter-otlp-proto-http>=1.34.1",
|
||||
"opentelemetry-instrumentation>=0.55b1",
|
||||
"opentelemetry-proto>=1.34.1",
|
||||
"opentelemetry-sdk>=1.34.1",
|
||||
"opentelemetry-semantic-conventions>=0.55b1",
|
||||
"opt-einsum>=3.4.0",
|
||||
"orjson>=3.10.18",
|
||||
"outcome>=1.3.0.post0",
|
||||
"packaging>=24.2",
|
||||
"pandas>=2.3.0",
|
||||
"pango>=0.0.1",
|
||||
"pdfkit>=1.0.0",
|
||||
"phonenumbers>=9.0.8",
|
||||
"pillow>=11.2.1",
|
||||
"platformdirs>=4.3.8",
|
||||
"prometheus-client>=0.22.1",
|
||||
"prompt-toolkit>=3.0.51",
|
||||
"propcache>=0.3.2",
|
||||
"protobuf>=5.29.5",
|
||||
"psycopg>=3.2.9",
|
||||
"psycopg-binary>=3.2.9",
|
||||
"psycopg-c>=3.2.9",
|
||||
"psycopg2-binary>=2.9.10",
|
||||
"py-moneyed>=3.0",
|
||||
"pyasn1>=0.6.1",
|
||||
"pyasn1-modules>=0.4.2",
|
||||
"pyautogui>=0.9.54",
|
||||
"pyclipper>=1.3.0.post6",
|
||||
"pycodestyle>=2.14.0",
|
||||
"pycparser>=2.22",
|
||||
"pydantic>=2.11.7",
|
||||
"pydantic-ai>=0.3.4",
|
||||
"pydantic-ai-slim>=0.3.4",
|
||||
"pydantic-core>=2.33.2",
|
||||
"pydantic-evals>=0.3.4",
|
||||
"pydantic-graph>=0.3.4",
|
||||
"pydantic-settings>=2.10.1",
|
||||
"pydotplus>=2.0.2",
|
||||
"pydyf>=0.11.0",
|
||||
"pygetwindow>=0.0.9",
|
||||
"pygments>=2.19.2",
|
||||
"pyjwt>=2.9.0",
|
||||
"pylint>=3.3.7",
|
||||
"pymsgbox>=1.0.9",
|
||||
"pymysql>=1.1.1",
|
||||
"pyobjc-core>=11.1",
|
||||
"pyobjc-framework-cocoa>=11.1",
|
||||
"pyobjc-framework-quartz>=11.1",
|
||||
"pyparsing>=3.2.3",
|
||||
"pypdf>=5.6.1",
|
||||
"pyperclip>=1.9.0",
|
||||
"pyphen>=0.17.2",
|
||||
"pypng>=0.20220715.0",
|
||||
"pyrect>=0.2.0",
|
||||
"pyscreeze>=1.0.1",
|
||||
"pyserial>=3.5",
|
||||
"pysocks>=1.7.1",
|
||||
"python-bidi>=0.6.6",
|
||||
"python-dateutil>=2.9.0.post0",
|
||||
"python-docx>=1.2.0",
|
||||
"python-dotenv>=1.1.1",
|
||||
"python-multipart>=0.0.20",
|
||||
"python-openid>=2.2.5",
|
||||
"python-slugify>=8.0.4",
|
||||
"python-stdnum>=2.1",
|
||||
"python3-saml>=1.16.0",
|
||||
"pytweening>=1.2.0",
|
||||
"pytz>=2025.2",
|
||||
"pyvin>=0.0.2",
|
||||
"pywa>=2.11.0",
|
||||
"pywhat>=1.0.0",
|
||||
"pywhatkit>=5.4",
|
||||
"pyyaml>=6.0.2",
|
||||
"pyzbar>=0.1.9",
|
||||
"qrcode>=8.2",
|
||||
"rapidfuzz>=3.13.0",
|
||||
"redis>=6.2.0",
|
||||
"regex>=2024.11.6",
|
||||
"reportlab>=4.4.2",
|
||||
"requests>=2.32.4",
|
||||
"requests-oauthlib>=2.0.0",
|
||||
"requests-toolbelt>=1.0.0",
|
||||
"rfc3986>=2.0.0",
|
||||
"rich>=14.0.0",
|
||||
"rsa>=4.9.1",
|
||||
"rubicon-objc>=0.5.1",
|
||||
"s3transfer>=0.13.0",
|
||||
"sacremoses>=0.1.1",
|
||||
"safetensors>=0.5.3",
|
||||
"scikit-image>=0.25.2",
|
||||
"scikit-learn>=1.7.0",
|
||||
"scipy>=1.16.0",
|
||||
"selenium>=4.32.0",
|
||||
"sentence-transformers>=4.1.0",
|
||||
"sentencepiece>=0.2.0",
|
||||
"shapely>=2.1.1",
|
||||
"simsimd>=6.4.9",
|
||||
"six>=1.17.0",
|
||||
"slugify>=0.0.1",
|
||||
"sniffio>=1.3.1",
|
||||
"snowballstemmer>=3.0.1",
|
||||
"sortedcontainers>=2.4.0",
|
||||
"soupsieve>=2.7",
|
||||
"sqlalchemy>=2.0.41",
|
||||
"sqlparse>=0.5.3",
|
||||
"sse-starlette>=2.3.6",
|
||||
"stanza>=1.10.1",
|
||||
"starlette>=0.47.1",
|
||||
"stringzilla>=3.12.5",
|
||||
"suds>=1.2.0",
|
||||
"swapper>=1.3.0",
|
||||
"sympy>=1.14.0",
|
||||
"tablib>=3.8.0",
|
||||
"tenacity>=8.5.0",
|
||||
"termcolor>=3.1.0",
|
||||
"text-unidecode>=1.3",
|
||||
"threadpoolctl>=3.6.0",
|
||||
"tifffile>=2025.6.11",
|
||||
"tinycss2>=1.4.0",
|
||||
"tinyhtml5>=2.0.0",
|
||||
"tokenizers>=0.21.2",
|
||||
"tomli>=2.2.1",
|
||||
"tomlkit>=0.13.3",
|
||||
"torch>=2.7.1",
|
||||
"tqdm>=4.67.1",
|
||||
"transformers>=4.52.4",
|
||||
"trio>=0.30.0",
|
||||
"trio-websocket>=0.12.2",
|
||||
"twilio>=9.6.3",
|
||||
"types-python-dateutil>=2.9.0.20250516",
|
||||
"types-requests>=2.32.4.20250611",
|
||||
"typing-extensions>=4.14.0",
|
||||
"typing-inspect>=0.9.0",
|
||||
"typing-inspection>=0.4.1",
|
||||
"tzdata>=2025.2",
|
||||
"unidecode>=1.4.0",
|
||||
"upgrade-requirements>=1.7.0",
|
||||
"urllib3>=2.5.0",
|
||||
"uvicorn>=0.34.3",
|
||||
"vin>=0.6.2",
|
||||
"vininfo>=1.9.1",
|
||||
"vishap>=0.1.5",
|
||||
"vpic-api>=0.7.4",
|
||||
"wcwidth>=0.2.13",
|
||||
"weasyprint>=65.1",
|
||||
"webencodings>=0.5.1",
|
||||
"websocket-client>=1.8.0",
|
||||
"websockets>=15.0.1",
|
||||
"werkzeug>=3.1.3",
|
||||
"wikipedia>=1.4.0",
|
||||
"wrapt>=1.17.2",
|
||||
"wsproto>=1.2.0",
|
||||
"xmlsec>=1.3.15",
|
||||
"yarl>=1.20.1",
|
||||
"zipp>=3.23.0",
|
||||
"zopfli>=0.2.3.post1",
|
||||
"zstandard>=0.23.0",
|
||||
]
|
||||
700
requirements.txt
700
requirements.txt
@ -1,350 +1,350 @@
|
||||
aiohappyeyeballs==2.6.1
|
||||
aiohttp==3.12.0
|
||||
aiohttp-retry==2.9.1
|
||||
aiosignal==1.3.2
|
||||
alabaster==1.0.0
|
||||
albucore==0.0.24
|
||||
albumentations==2.0.7
|
||||
annotated-types==0.7.0
|
||||
anthropic==0.52.2
|
||||
anyio==4.9.0
|
||||
arabic-reshaper==3.0.0
|
||||
argcomplete==3.6.2
|
||||
arrow==1.3.0
|
||||
asgiref==3.8.1
|
||||
astor==0.8.1
|
||||
astroid==3.3.10
|
||||
attrs==25.3.0
|
||||
autopep8==2.3.2
|
||||
Babel==2.15.0
|
||||
beautifulsoup4==4.13.4
|
||||
bleach==6.2.0
|
||||
blessed==1.21.0
|
||||
blinker==1.9.0
|
||||
boto3==1.38.29
|
||||
botocore==1.38.29
|
||||
Brotli==1.1.0
|
||||
cachetools==5.5.2
|
||||
cattrs==24.1.3
|
||||
certifi==2025.4.26
|
||||
cffi==1.17.1
|
||||
chardet==5.2.0
|
||||
charset-normalizer==3.4.2
|
||||
click==8.2.1
|
||||
cohere==5.15.0
|
||||
colorama==0.4.6
|
||||
commonmark==0.9.1
|
||||
contourpy==1.3.2
|
||||
crispy-bootstrap5==2025.4
|
||||
cryptography==45.0.3
|
||||
cssselect2==0.8.0
|
||||
ctranslate2==4.6.0
|
||||
cycler==0.12.1
|
||||
Cython==3.1.1
|
||||
dataclasses-json==0.6.7
|
||||
decorator==5.2.1
|
||||
defusedxml==0.7.1
|
||||
desert==2020.11.18
|
||||
diff-match-patch==20241021
|
||||
dill==0.4.0
|
||||
distro==1.9.0
|
||||
dj-rest-auth==7.0.1
|
||||
Django==5.2.1
|
||||
django-allauth==65.8.1
|
||||
django-appointment==3.8.0
|
||||
django-autoslug==1.9.9
|
||||
django-background-tasks==1.2.8
|
||||
django-bootstrap5==25.1
|
||||
django-ckeditor==6.7.2
|
||||
django-classy-tags==4.1.0
|
||||
django-cors-headers==4.7.0
|
||||
django-countries==7.6.1
|
||||
django-crispy-forms==2.4
|
||||
django-debug-toolbar==5.2.0
|
||||
django-easy-audit==1.3.7
|
||||
django-extensions==4.1
|
||||
django-filter==25.1
|
||||
django-formtools==2.5.1
|
||||
django-import-export==4.3.7
|
||||
django-js-asset==3.1.2
|
||||
django-ledger==0.7.7
|
||||
django-model-utils==5.0.0
|
||||
django-money==3.5.4
|
||||
django-next-url-mixin==0.4.0
|
||||
django-nine==0.2.7
|
||||
django-nonefield==0.4
|
||||
django-ordered-model==3.7.4
|
||||
django-pdf-actions==0.1.49
|
||||
django-phonenumber-field==8.0.0
|
||||
django-picklefield==3.3
|
||||
django-plans==2.0.0
|
||||
django-prometheus==2.3.1
|
||||
django-q2==1.8.0
|
||||
django-schema-graph==3.1.0
|
||||
django-sekizai==4.1.0
|
||||
django-sequences==3.0
|
||||
django-silk==5.3.2
|
||||
django-simple-history==3.8.0
|
||||
django-sms==0.7.0
|
||||
django-sslserver==0.22
|
||||
django-tables2==2.7.5
|
||||
django-treebeard==4.7.1
|
||||
django-view-breadcrumbs==2.5.1
|
||||
django-widget-tweaks==1.5.0
|
||||
djangocms-admin-style==3.3.1
|
||||
djangorestframework==3.16.0
|
||||
djangorestframework_simplejwt==5.5.0
|
||||
djangoviz==0.1.1
|
||||
djhtml==3.0.8
|
||||
docopt==0.6.2
|
||||
docutils==0.21.2
|
||||
easy-thumbnails==2.10
|
||||
emoji==2.14.1
|
||||
et_xmlfile==2.0.0
|
||||
eval_type_backport==0.2.2
|
||||
executing==2.2.0
|
||||
Faker==37.3.0
|
||||
fasta2a==0.2.14
|
||||
fastavro==1.11.1
|
||||
filelock==3.18.0
|
||||
fire==0.7.0
|
||||
fonttools==4.58.0
|
||||
fpdf==1.7.2
|
||||
fpdf2==2.8.3
|
||||
frozenlist==1.6.0
|
||||
fsspec==2025.5.1
|
||||
google-auth==2.40.2
|
||||
google-genai==1.18.0
|
||||
googleapis-common-protos==1.70.0
|
||||
gprof2dot==2025.4.14
|
||||
graphqlclient==0.2.4
|
||||
greenlet==3.2.2
|
||||
griffe==1.7.3
|
||||
groq==0.26.0
|
||||
h11==0.16.0
|
||||
h2==4.2.0
|
||||
hf-xet==1.1.3
|
||||
hpack==4.1.0
|
||||
hstspreload==2025.1.1
|
||||
httpcore==1.0.9
|
||||
httpx==0.28.1
|
||||
httpx-sse==0.4.0
|
||||
huggingface-hub==0.32.4
|
||||
hyperframe==6.1.0
|
||||
icalendar==6.3.1
|
||||
idna==3.10
|
||||
imageio==2.37.0
|
||||
imagesize==1.4.1
|
||||
imgaug==0.4.0
|
||||
importlib_metadata==8.7.0
|
||||
iso4217==1.12.20240625
|
||||
isodate==0.7.2
|
||||
isort==6.0.1
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
jiter==0.10.0
|
||||
jmespath==1.0.1
|
||||
joblib==1.5.1
|
||||
jsonpatch==1.33
|
||||
jsonpointer==3.0.0
|
||||
jwt==1.3.1
|
||||
kiwisolver==1.4.8
|
||||
langchain==0.3.25
|
||||
langchain-community==0.3.24
|
||||
langchain-core==0.3.61
|
||||
langchain-ollama==0.3.3
|
||||
langchain-text-splitters==0.3.8
|
||||
langsmith==0.3.42
|
||||
lazy_loader==0.4
|
||||
ledger==1.0.1
|
||||
libretranslatepy==2.1.4
|
||||
lmdb==1.6.2
|
||||
logfire==3.18.0
|
||||
logfire-api==3.17.0
|
||||
luhnchecker==0.0.12
|
||||
lxml==5.4.0
|
||||
Markdown==3.8
|
||||
markdown-it-py==3.0.0
|
||||
MarkupSafe==3.0.2
|
||||
marshmallow==3.26.1
|
||||
matplotlib==3.10.3
|
||||
mccabe==0.7.0
|
||||
mcp==1.9.2
|
||||
mdurl==0.1.2
|
||||
mistralai==1.8.1
|
||||
MouseInfo==0.1.3
|
||||
mpmath==1.3.0
|
||||
multidict==6.4.4
|
||||
mypy_extensions==1.1.0
|
||||
networkx==3.4.2
|
||||
newrelic==10.12.0
|
||||
nltk==3.9.1
|
||||
num2words==0.5.14
|
||||
numpy==2.2.6
|
||||
oauthlib==3.2.2
|
||||
ofxtools==0.9.5
|
||||
ollama==0.4.8
|
||||
openai==1.82.0
|
||||
opencv-contrib-python==4.11.0.86
|
||||
opencv-python==4.11.0.86
|
||||
opencv-python-headless==4.11.0.86
|
||||
openpyxl==3.1.5
|
||||
opentelemetry-api==1.34.0
|
||||
opentelemetry-exporter-otlp-proto-common==1.34.0
|
||||
opentelemetry-exporter-otlp-proto-http==1.34.0
|
||||
opentelemetry-instrumentation==0.55b0
|
||||
opentelemetry-proto==1.34.0
|
||||
opentelemetry-sdk==1.34.0
|
||||
opentelemetry-semantic-conventions==0.55b0
|
||||
opt_einsum==3.4.0
|
||||
orjson==3.10.18
|
||||
outcome==1.3.0.post0
|
||||
packaging==24.2
|
||||
pandas==2.2.3
|
||||
pango==0.0.1
|
||||
pdfkit==1.0.0
|
||||
phonenumbers==8.13.42
|
||||
pillow==10.4.0
|
||||
platformdirs==4.3.8
|
||||
prometheus_client==0.22.0
|
||||
prompt_toolkit==3.0.51
|
||||
propcache==0.3.1
|
||||
protobuf==5.29.5
|
||||
psycopg==3.2.9
|
||||
psycopg-binary==3.2.9
|
||||
psycopg-c==3.2.9
|
||||
psycopg2-binary==2.9.10
|
||||
py-moneyed==3.0
|
||||
pyasn1==0.6.1
|
||||
pyasn1_modules==0.4.2
|
||||
PyAutoGUI==0.9.54
|
||||
pyclipper==1.3.0.post6
|
||||
pycodestyle==2.13.0
|
||||
pycparser==2.22
|
||||
pydantic==2.11.5
|
||||
pydantic-ai==0.2.14
|
||||
pydantic-ai-slim==0.2.14
|
||||
pydantic-evals==0.2.14
|
||||
pydantic-graph==0.2.14
|
||||
pydantic-settings==2.9.1
|
||||
pydantic_core==2.33.2
|
||||
pydotplus==2.0.2
|
||||
pydyf==0.11.0
|
||||
PyGetWindow==0.0.9
|
||||
Pygments==2.19.1
|
||||
PyJWT==2.10.1
|
||||
pylint==3.3.7
|
||||
PyMsgBox==1.0.9
|
||||
PyMySQL==1.1.1
|
||||
pyobjc-core==11.0
|
||||
pyobjc-framework-Cocoa==11.0
|
||||
pyobjc-framework-Quartz==11.0
|
||||
pyparsing==3.2.3
|
||||
pypdf==5.5.0
|
||||
pyperclip==1.9.0
|
||||
pyphen==0.17.2
|
||||
pypng==0.20220715.0
|
||||
PyRect==0.2.0
|
||||
PyScreeze==1.0.1
|
||||
pyserial==3.5
|
||||
PySocks==1.7.1
|
||||
python-bidi==0.6.6
|
||||
python-dateutil==2.9.0.post0
|
||||
python-docx==1.1.2
|
||||
python-dotenv==1.1.0
|
||||
python-multipart==0.0.20
|
||||
python-openid==2.2.5
|
||||
python-slugify==8.0.4
|
||||
python-stdnum==2.1
|
||||
python3-saml==1.16.0
|
||||
pytweening==1.2.0
|
||||
pytz==2025.2
|
||||
pyvin==0.0.2
|
||||
pywa==2.10.0
|
||||
pywhat==5.1.0
|
||||
pywhatkit==5.4
|
||||
PyYAML==6.0.2
|
||||
pyzbar==0.1.9
|
||||
qrcode==8.2
|
||||
RapidFuzz==3.13.0
|
||||
redis==6.1.0
|
||||
regex==2024.11.6
|
||||
reportlab==4.4.1
|
||||
requests==2.32.3
|
||||
requests-oauthlib==2.0.0
|
||||
requests-toolbelt==1.0.0
|
||||
rfc3986==2.0.0
|
||||
rich==14.0.0
|
||||
rsa==4.9.1
|
||||
rubicon-objc==0.5.0
|
||||
s3transfer==0.13.0
|
||||
sacremoses==0.1.1
|
||||
safetensors==0.5.3
|
||||
scikit-image==0.25.2
|
||||
scikit-learn==1.6.1
|
||||
scipy==1.15.3
|
||||
selenium==4.33.0
|
||||
sentence-transformers==4.1.0
|
||||
sentencepiece==0.2.0
|
||||
shapely==2.1.1
|
||||
simsimd==6.2.1
|
||||
six==1.17.0
|
||||
slugify==0.0.1
|
||||
sniffio==1.3.1
|
||||
snowballstemmer==3.0.1
|
||||
sortedcontainers==2.4.0
|
||||
soupsieve==2.7
|
||||
SQLAlchemy==2.0.41
|
||||
sqlparse==0.5.3
|
||||
sse-starlette==2.3.6
|
||||
stanza==1.10.1
|
||||
starlette==0.47.0
|
||||
stringzilla==3.12.5
|
||||
suds==1.2.0
|
||||
swapper==1.3.0
|
||||
sympy==1.14.0
|
||||
tablib==3.8.0
|
||||
tenacity==9.1.2
|
||||
termcolor==3.1.0
|
||||
text-unidecode==1.3
|
||||
threadpoolctl==3.6.0
|
||||
tifffile==2025.5.24
|
||||
tinycss2==1.4.0
|
||||
tinyhtml5==2.0.0
|
||||
tokenizers==0.21.1
|
||||
tomli==2.2.1
|
||||
tomlkit==0.13.2
|
||||
torch==2.7.0
|
||||
tqdm==4.67.1
|
||||
transformers==4.52.4
|
||||
trio==0.30.0
|
||||
trio-websocket==0.12.2
|
||||
twilio==9.6.1
|
||||
types-python-dateutil==2.9.0.20250516
|
||||
types-requests==2.32.0.20250602
|
||||
typing-inspect==0.9.0
|
||||
typing-inspection==0.4.1
|
||||
typing_extensions==4.13.2
|
||||
tzdata==2025.2
|
||||
Unidecode==1.4.0
|
||||
upgrade-requirements==1.7.0
|
||||
urllib3==2.4.0
|
||||
uvicorn==0.34.3
|
||||
vin==0.6.2
|
||||
vininfo==1.8.0
|
||||
vishap==0.1.5
|
||||
vpic-api==0.7.4
|
||||
wcwidth==0.2.13
|
||||
weasyprint==65.1
|
||||
webencodings==0.5.1
|
||||
websocket-client==1.8.0
|
||||
websockets==15.0.1
|
||||
Werkzeug==3.1.3
|
||||
wikipedia==1.4.0
|
||||
wrapt==1.17.2
|
||||
wsproto==1.2.0
|
||||
xmlsec==1.3.15
|
||||
yarl==1.20.0
|
||||
zipp==3.22.0
|
||||
zopfli==0.2.3.post1
|
||||
zstandard==0.23.0
|
||||
aiohappyeyeballs
|
||||
aiohttp
|
||||
aiohttp-retry
|
||||
aiosignal
|
||||
alabaster
|
||||
albucore
|
||||
albumentations
|
||||
annotated-types
|
||||
anthropic
|
||||
anyio
|
||||
arabic-reshaper
|
||||
argcomplete
|
||||
arrow
|
||||
asgiref
|
||||
astor
|
||||
astroid
|
||||
attrs
|
||||
autopep8
|
||||
Babel
|
||||
beautifulsoup4
|
||||
bleach
|
||||
blessed
|
||||
blinker
|
||||
boto3
|
||||
botocore
|
||||
Brotli
|
||||
cachetools
|
||||
cattrs
|
||||
certifi
|
||||
cffi
|
||||
chardet
|
||||
charset-normalizer
|
||||
click
|
||||
cohere
|
||||
colorama
|
||||
commonmark
|
||||
contourpy
|
||||
crispy-bootstrap5
|
||||
cryptography
|
||||
cssselect2
|
||||
ctranslate2
|
||||
cycler
|
||||
Cython
|
||||
dataclasses-json
|
||||
decorator
|
||||
defusedxml
|
||||
desert
|
||||
diff-match-patch
|
||||
dill
|
||||
distro
|
||||
dj-rest-auth
|
||||
Django
|
||||
django-allauth
|
||||
django-appointment
|
||||
django-autoslug
|
||||
django-background-tasks
|
||||
django-bootstrap5
|
||||
django-ckeditor
|
||||
django-classy-tags
|
||||
django-cors-headers
|
||||
django-countries
|
||||
django-crispy-forms
|
||||
django-debug-toolbar
|
||||
django-easy-audit
|
||||
django-extensions
|
||||
django-filter
|
||||
django-formtools
|
||||
django-import-export
|
||||
django-js-asset
|
||||
django-ledger
|
||||
django-model-utils
|
||||
django-money
|
||||
django-next-url-mixin
|
||||
django-nine
|
||||
django-nonefield
|
||||
django-ordered-model
|
||||
django-pdf-actions
|
||||
django-phonenumber-field
|
||||
django-picklefield
|
||||
django-plans
|
||||
django-prometheus
|
||||
django-q2
|
||||
django-schema-graph
|
||||
django-sekizai
|
||||
django-sequences
|
||||
django-silk
|
||||
django-simple-history
|
||||
django-sms
|
||||
django-sslserver
|
||||
django-tables2
|
||||
django-treebeard
|
||||
django-view-breadcrumbs
|
||||
django-widget-tweaks
|
||||
djangocms-admin-style
|
||||
djangorestframework
|
||||
djangorestframework_simplejwt
|
||||
djangoviz
|
||||
djhtml
|
||||
docopt
|
||||
docutils
|
||||
easy-thumbnails
|
||||
emoji
|
||||
et_xmlfile
|
||||
eval_type_backport
|
||||
executing
|
||||
Faker
|
||||
fasta2a
|
||||
fastavro
|
||||
filelock
|
||||
fire
|
||||
fonttools
|
||||
fpdf
|
||||
fpdf2
|
||||
frozenlist
|
||||
fsspec
|
||||
google-auth
|
||||
google-genai
|
||||
googleapis-common-protos
|
||||
gprof2dot
|
||||
graphqlclient
|
||||
greenlet
|
||||
griffe
|
||||
groq
|
||||
h11
|
||||
h2
|
||||
hf-xet
|
||||
hpack
|
||||
hstspreload
|
||||
httpcore
|
||||
httpx
|
||||
httpx-sse
|
||||
huggingface-hub
|
||||
hyperframe
|
||||
icalendar
|
||||
idna
|
||||
imageio
|
||||
imagesize
|
||||
imgaug
|
||||
importlib_metadata
|
||||
iso4217
|
||||
isodate
|
||||
isort
|
||||
itsdangerous
|
||||
Jinja2
|
||||
jiter
|
||||
jmespath
|
||||
joblib
|
||||
jsonpatch
|
||||
jsonpointer
|
||||
jwt
|
||||
kiwisolver
|
||||
langchain
|
||||
langchain-community
|
||||
langchain-core
|
||||
langchain-ollama
|
||||
langchain-text-splitters
|
||||
langsmith
|
||||
lazy_loader
|
||||
ledger
|
||||
libretranslatepy
|
||||
lmdb
|
||||
logfire
|
||||
logfire-api
|
||||
luhnchecker
|
||||
lxml
|
||||
Markdown
|
||||
markdown-it-py
|
||||
MarkupSafe
|
||||
marshmallow
|
||||
matplotlib
|
||||
mccabe
|
||||
mcp
|
||||
mdurl
|
||||
mistralai
|
||||
MouseInfo
|
||||
mpmath
|
||||
multidict
|
||||
mypy_extensions
|
||||
networkx
|
||||
newrelic
|
||||
nltk
|
||||
num2words
|
||||
numpy
|
||||
oauthlib
|
||||
ofxtools
|
||||
ollama
|
||||
openai
|
||||
opencv-contrib-python
|
||||
opencv-python
|
||||
opencv-python-headless
|
||||
openpyxl
|
||||
opentelemetry-api
|
||||
opentelemetry-exporter-otlp-proto-common
|
||||
opentelemetry-exporter-otlp-proto-http
|
||||
opentelemetry-instrumentation
|
||||
opentelemetry-proto
|
||||
opentelemetry-sdk
|
||||
opentelemetry-semantic-conventions
|
||||
opt_einsum
|
||||
orjson
|
||||
outcome
|
||||
packaging
|
||||
pandas
|
||||
pango
|
||||
pdfkit
|
||||
phonenumbers
|
||||
pillow
|
||||
platformdirs
|
||||
prometheus_client
|
||||
prompt_toolkit
|
||||
propcache
|
||||
protobuf
|
||||
psycopg
|
||||
psycopg-binary
|
||||
psycopg-c
|
||||
psycopg2-binary
|
||||
py-moneyed
|
||||
pyasn1
|
||||
pyasn1_modules
|
||||
PyAutoGUI
|
||||
pyclipper
|
||||
pycodestyle
|
||||
pycparser
|
||||
pydantic
|
||||
pydantic-ai
|
||||
pydantic-ai-slim
|
||||
pydantic-evals
|
||||
pydantic-graph
|
||||
pydantic-settings
|
||||
pydantic_core
|
||||
pydotplus
|
||||
pydyf
|
||||
PyGetWindow
|
||||
Pygments
|
||||
PyJWT
|
||||
pylint
|
||||
PyMsgBox
|
||||
PyMySQL
|
||||
pyobjc-core
|
||||
pyobjc-framework-Cocoa
|
||||
pyobjc-framework-Quartz
|
||||
pyparsing
|
||||
pypdf
|
||||
pyperclip
|
||||
pyphen
|
||||
pypng
|
||||
PyRect
|
||||
PyScreeze
|
||||
pyserial
|
||||
PySocks
|
||||
python-bidi
|
||||
python-dateutil
|
||||
python-docx
|
||||
python-dotenv
|
||||
python-multipart
|
||||
python-openid
|
||||
python-slugify
|
||||
python-stdnum
|
||||
python3-saml
|
||||
pytweening
|
||||
pytz
|
||||
pyvin
|
||||
pywa
|
||||
pywhat
|
||||
pywhatkit
|
||||
PyYAML
|
||||
pyzbar
|
||||
qrcode
|
||||
RapidFuzz
|
||||
redis
|
||||
regex
|
||||
reportlab
|
||||
requests
|
||||
requests-oauthlib
|
||||
requests-toolbelt
|
||||
rfc3986
|
||||
rich
|
||||
rsa
|
||||
rubicon-objc
|
||||
s3transfer
|
||||
sacremoses
|
||||
safetensors
|
||||
scikit-image
|
||||
scikit-learn
|
||||
scipy
|
||||
selenium
|
||||
sentence-transformers
|
||||
sentencepiece
|
||||
shapely
|
||||
simsimd
|
||||
six
|
||||
slugify
|
||||
sniffio
|
||||
snowballstemmer
|
||||
sortedcontainers
|
||||
soupsieve
|
||||
SQLAlchemy
|
||||
sqlparse
|
||||
sse-starlette
|
||||
stanza
|
||||
starlette
|
||||
stringzilla
|
||||
suds
|
||||
swapper
|
||||
sympy
|
||||
tablib
|
||||
tenacity
|
||||
termcolor
|
||||
text-unidecode
|
||||
threadpoolctl
|
||||
tifffile
|
||||
tinycss2
|
||||
tinyhtml5
|
||||
tokenizers
|
||||
tomli
|
||||
tomlkit
|
||||
torch
|
||||
tqdm
|
||||
transformers
|
||||
trio
|
||||
trio-websocket
|
||||
twilio
|
||||
types-python-dateutil
|
||||
types-requests
|
||||
typing-inspect
|
||||
typing-inspection
|
||||
typing_extensions
|
||||
tzdata
|
||||
Unidecode
|
||||
upgrade-requirements
|
||||
urllib3
|
||||
uvicorn
|
||||
vin
|
||||
vininfo
|
||||
vishap
|
||||
vpic-api
|
||||
wcwidth
|
||||
weasyprint
|
||||
webencodings
|
||||
websocket-client
|
||||
websockets
|
||||
Werkzeug
|
||||
wikipedia
|
||||
wrapt
|
||||
wsproto
|
||||
xmlsec
|
||||
yarl
|
||||
zipp
|
||||
zopfli
|
||||
zstandard
|
||||
|
||||
BIN
static/.DS_Store
vendored
BIN
static/.DS_Store
vendored
Binary file not shown.
BIN
static/images/.DS_Store
vendored
BIN
static/images/.DS_Store
vendored
Binary file not shown.
BIN
static/images/cars/.DS_Store
vendored
BIN
static/images/cars/.DS_Store
vendored
Binary file not shown.
@ -19,6 +19,3 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,90 +1,64 @@
|
||||
{% load i18n static %}
|
||||
<div class="d-flex justify-content-between align-items-center mt-4 mb-3">
|
||||
<div class="text-body-secondary">
|
||||
{{ _("Showing") }} {{ page_obj.start_index }} {{ _("to") }} {{ page_obj.end_index }}
|
||||
{{ _("of") }} {{ page_obj.paginator.count }} {{ _("results") }}
|
||||
</div>
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination mb-0">
|
||||
<div class="text-body-secondary fw-bold fs-10">
|
||||
{{ _("Showing") }} {{ page_obj.start_index }} {{ _("to") }} {{ page_obj.end_index }}
|
||||
{{ _("of") }} {{ page_obj.paginator.count }} {{ _("results") }}
|
||||
</div>
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination mb-0">
|
||||
{# First Page Link #}
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item rounded-md overflow-hidden">
|
||||
<a class="page-link px-3 py-2 border border-gray-300 bg-white text-blue-600 hover:bg-gray-100 transition-colors duration-200" href="?page=1{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'First' %}">
|
||||
<span class="fas fa-angle-double-left" aria-hidden="true"></span>
|
||||
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled rounded-md overflow-hidden">
|
||||
<span class="page-link px-3 py-2 border border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed">
|
||||
<span class="fas fa-angle-double-left" aria-hidden="true"></span>
|
||||
|
||||
</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1{% if q %}&q={{q}}{% endif %}">
|
||||
<span class="fas fa-angle-double-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}"> </span>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item">
|
||||
<span class="page-link">
|
||||
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}"> </span>
|
||||
</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# Previous Page Link #}
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item rounded-md overflow-hidden">
|
||||
<a class="page-link px-3 py-2 border border-gray-300 bg-white text-blue-600 hover:bg-gray-100 transition-colors duration-200" href="?page={{ page_obj.previous_page_number }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Previous' %}">
|
||||
<span class="fas fa-chevron-left" aria-hidden="true"></span>
|
||||
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled rounded-md overflow-hidden">
|
||||
<span class="page-link px-3 py-2 border border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed">
|
||||
<span class="fas fa-chevron-left" aria-hidden="true"></span>
|
||||
|
||||
</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if q %}&q={{q}}{% endif %}">
|
||||
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# Page Numbers #}
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if num == 1 or num == page_obj.paginator.num_pages or num >= page_obj.number|add:-2 and num <= page_obj.number|add:2 %}
|
||||
<li class="page-item {% if num == page_obj.number %}active{% endif %}">
|
||||
<a class="page-link" {% if num == page_obj.number %}aria-current="page"{% endif %}
|
||||
href="?page={{ num }}{% if q %}&q={{q}}{% endif %}">
|
||||
{{ num }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if num == 1 or num == page_obj.paginator.num_pages or num >= page_obj.number|add:-2 and num <= page_obj.number|add:2 %}
|
||||
<li class="page-item {% if num == page_obj.number %}active{% endif %}">
|
||||
<a class="page-link" {% if num == page_obj.number %}aria-current="page"{% endif %}
|
||||
href="?page={{ num }}{% if q %}&q={{q}}{% endif %}">
|
||||
{{ num }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{# Next Page Link #}
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item rounded-md overflow-hidden">
|
||||
<a class="page-link px-3 py-2 border border-gray-300 bg-white text-blue-600 hover:bg-gray-100 transition-colors duration-200" href="?page={{ page_obj.next_page_number }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Next' %}">
|
||||
<span class="fas fa-chevron-right" aria-hidden="true"></span>
|
||||
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled rounded-md overflow-hidden">
|
||||
<span class="page-link px-3 py-2 border border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed">
|
||||
<span class="fas fa-chevron-right" aria-hidden="true"></span>
|
||||
|
||||
</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if q %}&q={{q}}{% endif %}">
|
||||
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}left{% else %}right{% endif %}"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# Last Page Link #}
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item rounded-md overflow-hidden">
|
||||
<a class="page-link px-3 py-2 border border-gray-300 bg-white text-blue-600 hover:bg-gray-100 transition-colors duration-200" href="?page={{ page_obj.paginator.num_pages }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Last' %}">
|
||||
<span class="fas fa-angle-double-right" aria-hidden="true"></span>
|
||||
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled rounded-md overflow-hidden">
|
||||
<span class="page-link px-3 py-2 border border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed">
|
||||
<span class="fas fa-angle-double-right" aria-hidden="true"></span>
|
||||
|
||||
</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if q %}&q={{q}}{% endif %}">
|
||||
<span class="fas fa-angle-double-{% if LANGUAGE_CODE == 'ar' %}left{% else %}right{% endif %}"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
@ -2,5 +2,5 @@ from django.contrib import admin
|
||||
from . import models
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(models.Tour)
|
||||
admin.site.register(models.TourCompletion)
|
||||
# admin.site.register(models.Tour)
|
||||
# admin.site.register(models.TourCompletion)
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-06 14:05
|
||||
|
||||
import django.db.models.deletion
|
||||
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="Tour",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=100)),
|
||||
("description", models.TextField(blank=True)),
|
||||
("slug", models.SlugField(unique=True)),
|
||||
("tour_file", models.CharField(max_length=255)),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TourCompletion",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("completed_on", models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"tour",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="tours.tour"
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"unique_together": {("tour", "user")},
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -1,21 +1,21 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Tour(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
description = models.TextField(blank=True)
|
||||
slug = models.SlugField(unique=True)
|
||||
tour_file = models.CharField(max_length=255)
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class TourCompletion(models.Model):
|
||||
tour = models.ForeignKey(Tour, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey("auth.User", on_delete=models.CASCADE)
|
||||
completed_on = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("tour", "user")
|
||||
# class Tour(models.Model):
|
||||
# name = models.CharField(max_length=100)
|
||||
# description = models.TextField(blank=True)
|
||||
# slug = models.SlugField(unique=True)
|
||||
# tour_file = models.CharField(max_length=255)
|
||||
# is_active = models.BooleanField(default=True)
|
||||
#
|
||||
# def __str__(self):
|
||||
# return self.name
|
||||
#
|
||||
#
|
||||
# class TourCompletion(models.Model):
|
||||
# tour = models.ForeignKey(Tour, on_delete=models.CASCADE)
|
||||
# user = models.ForeignKey("auth.User", on_delete=models.CASCADE)
|
||||
# completed_on = models.DateTimeField(auto_now_add=True)
|
||||
#
|
||||
# class Meta:
|
||||
# unique_together = ("tour", "user")
|
||||
|
||||
@ -2,8 +2,8 @@ from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.tour_list, name="tour_list"),
|
||||
path("data/<slug:slug>/", views.get_tour_data, name="get_tour_data"),
|
||||
path("complete/<slug:slug>/", views.mark_tour_completed, name="mark_tour_complete"),
|
||||
path("start/<slug:slug>/", views.start_tour_view, name="start_tour"),
|
||||
# path("", views.tour_list, name="tour_list"),
|
||||
# path("data/<slug:slug>/", views.get_tour_data, name="get_tour_data"),
|
||||
# path("complete/<slug:slug>/", views.mark_tour_completed, name="mark_tour_complete"),
|
||||
# path("start/<slug:slug>/", views.start_tour_view, name="start_tour"),
|
||||
]
|
||||
|
||||
108
tours/views.py
108
tours/views.py
@ -1,54 +1,54 @@
|
||||
import os
|
||||
import json
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.http import JsonResponse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.conf import settings
|
||||
from .models import Tour, TourCompletion
|
||||
|
||||
|
||||
@login_required
|
||||
def tour_list(request):
|
||||
tours = Tour.objects.filter(is_active=True)
|
||||
return render(request, "tours/tour_list.html", {"tours": tours})
|
||||
|
||||
|
||||
@login_required
|
||||
def get_tour_data(request, slug):
|
||||
tour = get_object_or_404(Tour, slug=slug, is_active=True)
|
||||
|
||||
# Check if user has already completed this tour
|
||||
completed = TourCompletion.objects.filter(tour=tour, user=request.user).exists()
|
||||
|
||||
# Load the tour data from JSON file
|
||||
tour_file_path = os.path.join(
|
||||
settings.BASE_DIR, "static", "js", "tours", tour.tour_file
|
||||
)
|
||||
|
||||
try:
|
||||
with open(tour_file_path, "r") as f:
|
||||
tour_data = json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
return JsonResponse({"error": "Tour data not found or invalid"}, status=404)
|
||||
|
||||
return JsonResponse({"tour": tour_data, "completed": completed})
|
||||
|
||||
|
||||
@login_required
|
||||
def mark_tour_completed(request, slug):
|
||||
if request.method != "POST":
|
||||
return JsonResponse({"error": "Method not allowed"}, status=405)
|
||||
|
||||
tour = get_object_or_404(Tour, slug=slug, is_active=True)
|
||||
|
||||
# Mark the tour as completed for this user
|
||||
TourCompletion.objects.get_or_create(tour=tour, user=request.user)
|
||||
|
||||
return JsonResponse({"status": "success"})
|
||||
|
||||
|
||||
@login_required
|
||||
def start_tour_view(request, slug):
|
||||
tour = get_object_or_404(Tour, slug=slug, is_active=True)
|
||||
# Redirect to the page where the tour should start
|
||||
return render(request, "tours/start_tour.html", {"tour": tour})
|
||||
# import os
|
||||
# import json
|
||||
# from django.shortcuts import render, get_object_or_404
|
||||
# from django.http import JsonResponse
|
||||
# from django.contrib.auth.decorators import login_required
|
||||
# from django.conf import settings
|
||||
# from .models import Tour, TourCompletion
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# def tour_list(request):
|
||||
# tours = Tour.objects.filter(is_active=True)
|
||||
# return render(request, "tours/tour_list.html", {"tours": tours})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# def get_tour_data(request, slug):
|
||||
# tour = get_object_or_404(Tour, slug=slug, is_active=True)
|
||||
#
|
||||
# # Check if user has already completed this tour
|
||||
# completed = TourCompletion.objects.filter(tour=tour, user=request.user).exists()
|
||||
#
|
||||
# # Load the tour data from JSON file
|
||||
# tour_file_path = os.path.join(
|
||||
# settings.BASE_DIR, "static", "js", "tours", tour.tour_file
|
||||
# )
|
||||
#
|
||||
# try:
|
||||
# with open(tour_file_path, "r") as f:
|
||||
# tour_data = json.load(f)
|
||||
# except (FileNotFoundError, json.JSONDecodeError):
|
||||
# return JsonResponse({"error": "Tour data not found or invalid"}, status=404)
|
||||
#
|
||||
# return JsonResponse({"tour": tour_data, "completed": completed})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# def mark_tour_completed(request, slug):
|
||||
# if request.method != "POST":
|
||||
# return JsonResponse({"error": "Method not allowed"}, status=405)
|
||||
#
|
||||
# tour = get_object_or_404(Tour, slug=slug, is_active=True)
|
||||
#
|
||||
# # Mark the tour as completed for this user
|
||||
# TourCompletion.objects.get_or_create(tour=tour, user=request.user)
|
||||
#
|
||||
# return JsonResponse({"status": "success"})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# def start_tour_view(request, slug):
|
||||
# tour = get_object_or_404(Tour, slug=slug, is_active=True)
|
||||
# # Redirect to the page where the tour should start
|
||||
# return render(request, "tours/start_tour.html", {"tour": tour})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user