diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..f804ad10 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,22 @@ +__pycache__/ +*.pyc +*.log +*.sqlite3 +*.db +migrations/ +media/ +static/ +node_modules/ +venv/ +env/ +.git/ +.gitignore +.DS_Store +docker-compose.yml +README.md +car*.json +.vscode +.idea +*/migrations/* +*/migrations/ +inventory/migrations/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..eacc006c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# Use an official Python image as a base +FROM python:3.11.11-slim-bullseye + +# Set the working directory to /app +WORKDIR /app + +# Create a new user and group +RUN groupadd -r appgroup +RUN useradd -r -g appgroup -G appgroup -m -d /app -s /bin/false appuser + +# Copy the requirements file +COPY requirements.txt . + +# Install the dependencies +RUN pip install -r requirements.txt +RUN apt-get update && apt-get install -y libgl1 +RUN apt-get update && apt-get install -y libglib2.0-dev +RUN apt-get update && apt-get install -y libzbar0 + +# Copy the application code +COPY . . + +# Expose the port +EXPOSE 8000 + +# Copy the entrypoint script +COPY entrypoint.sh /app/entrypoint.sh + +# Make the script executable +RUN chmod +x /app/entrypoint.sh + +# Change ownership of the app directory to the new user +RUN chown -R appuser:appgroup /app + +RUN find /app -path "*/migrations/*.py" -not -name "__init__.py" -delete +RUN find /app -path "*/migrations/*.pyc" -delete + +# Set the entrypoint to execute the script as the new user +ENTRYPOINT ["sh", "-c", "python3 manage.py makemigrations && python3 manage.py migrate && python3 manage.py collectstatic --no-input && python3 manage.py runserver 0.0.0.0:8000"] \ No newline at end of file diff --git a/api/__pycache__/__init__.cpython-311.pyc b/api/__pycache__/__init__.cpython-311.pyc index c463de3e..3224f17d 100644 Binary files a/api/__pycache__/__init__.cpython-311.pyc and b/api/__pycache__/__init__.cpython-311.pyc differ diff --git a/api/__pycache__/admin.cpython-311.pyc b/api/__pycache__/admin.cpython-311.pyc index 4612b35a..e8144647 100644 Binary files a/api/__pycache__/admin.cpython-311.pyc and b/api/__pycache__/admin.cpython-311.pyc differ diff --git a/api/__pycache__/apps.cpython-311.pyc b/api/__pycache__/apps.cpython-311.pyc index 77b80ab1..98145dab 100644 Binary files a/api/__pycache__/apps.cpython-311.pyc and b/api/__pycache__/apps.cpython-311.pyc differ diff --git a/api/__pycache__/models.cpython-311.pyc b/api/__pycache__/models.cpython-311.pyc index ec635e62..6d74ca0b 100644 Binary files a/api/__pycache__/models.cpython-311.pyc and b/api/__pycache__/models.cpython-311.pyc differ diff --git a/api/__pycache__/serializers.cpython-311.pyc b/api/__pycache__/serializers.cpython-311.pyc index b4949161..4d72b394 100644 Binary files a/api/__pycache__/serializers.cpython-311.pyc and b/api/__pycache__/serializers.cpython-311.pyc differ diff --git a/api/__pycache__/services.cpython-311.pyc b/api/__pycache__/services.cpython-311.pyc index 3d0cde4f..fb40e554 100644 Binary files a/api/__pycache__/services.cpython-311.pyc and b/api/__pycache__/services.cpython-311.pyc differ diff --git a/api/__pycache__/urls.cpython-311.pyc b/api/__pycache__/urls.cpython-311.pyc index d7507793..13f8cb5e 100644 Binary files a/api/__pycache__/urls.cpython-311.pyc and b/api/__pycache__/urls.cpython-311.pyc differ diff --git a/api/__pycache__/views.cpython-311.pyc b/api/__pycache__/views.cpython-311.pyc index 94bc6f61..05be440b 100644 Binary files a/api/__pycache__/views.cpython-311.pyc and b/api/__pycache__/views.cpython-311.pyc differ diff --git a/api/migrations/__pycache__/0001_initial.cpython-311.pyc b/api/migrations/__pycache__/0001_initial.cpython-311.pyc index d6ae357c..25d08435 100644 Binary files a/api/migrations/__pycache__/0001_initial.cpython-311.pyc and b/api/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/api/migrations/__pycache__/__init__.cpython-311.pyc b/api/migrations/__pycache__/__init__.cpython-311.pyc index 6be173e1..e249a135 100644 Binary files a/api/migrations/__pycache__/__init__.cpython-311.pyc and b/api/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/car_inventory/__pycache__/__init__.cpython-311.pyc b/car_inventory/__pycache__/__init__.cpython-311.pyc index cbc58d29..f2d679b7 100644 Binary files a/car_inventory/__pycache__/__init__.cpython-311.pyc and b/car_inventory/__pycache__/__init__.cpython-311.pyc differ diff --git a/car_inventory/__pycache__/settings.cpython-311.pyc b/car_inventory/__pycache__/settings.cpython-311.pyc index 02a390cc..76352677 100644 Binary files a/car_inventory/__pycache__/settings.cpython-311.pyc and b/car_inventory/__pycache__/settings.cpython-311.pyc differ diff --git a/car_inventory/__pycache__/urls.cpython-311.pyc b/car_inventory/__pycache__/urls.cpython-311.pyc index 710e63f3..77e3f7ad 100644 Binary files a/car_inventory/__pycache__/urls.cpython-311.pyc and b/car_inventory/__pycache__/urls.cpython-311.pyc differ diff --git a/car_inventory/__pycache__/wsgi.cpython-311.pyc b/car_inventory/__pycache__/wsgi.cpython-311.pyc index ada8fdac..86d4e317 100644 Binary files a/car_inventory/__pycache__/wsgi.cpython-311.pyc and b/car_inventory/__pycache__/wsgi.cpython-311.pyc differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..78c65691 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: '3' + +services: + db: + image: postgres + restart: always + environment: + - POSTGRES_DB=haikal_db + - POSTGRES_USER=haikal_user + - POSTGRES_PASSWORD=haikal_pass + healthcheck: + test: ["CMD", "pg_isready", "-U", "haikal_user", "-d", "haikal_db"] + interval: 1m30s + timeout: 30s + retries: 5 + start_period: 30s + web: + build: . + command: python manage.py runserver 0.0.0.0:8000 + environment: + - DATABASE_HOST=db + - DATABASE_PORT=5432 + - POSTGRES_DB=haikal_db + - POSTGRES_USER=haikal_user + - POSTGRES_PASSWORD=haikal_pass + volumes: + - .:/app + ports: + - "8000:8000" + depends_on: + - db \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 00000000..b3783692 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +python manage.py migrate +python manage.py collectstatic --no-input +python manage.py runserver 0.0.0.0:8000 \ No newline at end of file diff --git a/haikalbot/migrations/0001_initial.py b/haikalbot/migrations/0001_initial.py index 2f118a31..40c4a0ce 100644 --- a/haikalbot/migrations/0001_initial.py +++ b/haikalbot/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.5 on 2025-01-30 11:28 +# Generated by Django 5.1.6 on 2025-03-06 01:43 from django.db import migrations, models diff --git a/haikalbot/migrations/0002_initial.py b/haikalbot/migrations/0002_initial.py index 0b0861b7..2ef50ed1 100644 --- a/haikalbot/migrations/0002_initial.py +++ b/haikalbot/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.5 on 2025-01-30 11:28 +# Generated by Django 5.1.6 on 2025-03-06 01:43 import django.db.models.deletion from django.db import migrations, models diff --git a/inventory/__pycache__/__init__.cpython-311.pyc b/inventory/__pycache__/__init__.cpython-311.pyc index 767a888d..27c305b7 100644 Binary files a/inventory/__pycache__/__init__.cpython-311.pyc and b/inventory/__pycache__/__init__.cpython-311.pyc differ diff --git a/inventory/__pycache__/admin.cpython-311.pyc b/inventory/__pycache__/admin.cpython-311.pyc index 32b4af46..85149ff8 100644 Binary files a/inventory/__pycache__/admin.cpython-311.pyc and b/inventory/__pycache__/admin.cpython-311.pyc differ diff --git a/inventory/__pycache__/apps.cpython-311.pyc b/inventory/__pycache__/apps.cpython-311.pyc index 811af786..4fe06dca 100644 Binary files a/inventory/__pycache__/apps.cpython-311.pyc and b/inventory/__pycache__/apps.cpython-311.pyc differ diff --git a/inventory/__pycache__/filters.cpython-311.pyc b/inventory/__pycache__/filters.cpython-311.pyc index a30b11ba..7e88bd9c 100644 Binary files a/inventory/__pycache__/filters.cpython-311.pyc and b/inventory/__pycache__/filters.cpython-311.pyc differ diff --git a/inventory/__pycache__/forms.cpython-311.pyc b/inventory/__pycache__/forms.cpython-311.pyc index f041ae18..5c27f5ba 100644 Binary files a/inventory/__pycache__/forms.cpython-311.pyc and b/inventory/__pycache__/forms.cpython-311.pyc differ diff --git a/inventory/__pycache__/middleware.cpython-311.pyc b/inventory/__pycache__/middleware.cpython-311.pyc index ae0ab7a1..d7a65bcd 100644 Binary files a/inventory/__pycache__/middleware.cpython-311.pyc and b/inventory/__pycache__/middleware.cpython-311.pyc differ diff --git a/inventory/__pycache__/mixins.cpython-311.pyc b/inventory/__pycache__/mixins.cpython-311.pyc index 83a38ab9..44beb318 100644 Binary files a/inventory/__pycache__/mixins.cpython-311.pyc and b/inventory/__pycache__/mixins.cpython-311.pyc differ diff --git a/inventory/__pycache__/models.cpython-311.pyc b/inventory/__pycache__/models.cpython-311.pyc index 6de49907..38538031 100644 Binary files a/inventory/__pycache__/models.cpython-311.pyc and b/inventory/__pycache__/models.cpython-311.pyc differ diff --git a/inventory/__pycache__/services.cpython-311.pyc b/inventory/__pycache__/services.cpython-311.pyc index a6677523..b0f20d07 100644 Binary files a/inventory/__pycache__/services.cpython-311.pyc and b/inventory/__pycache__/services.cpython-311.pyc differ diff --git a/inventory/__pycache__/tables.cpython-311.pyc b/inventory/__pycache__/tables.cpython-311.pyc index 066c1013..85caef93 100644 Binary files a/inventory/__pycache__/tables.cpython-311.pyc and b/inventory/__pycache__/tables.cpython-311.pyc differ diff --git a/inventory/__pycache__/urls.cpython-311.pyc b/inventory/__pycache__/urls.cpython-311.pyc index e51a455c..2af2899c 100644 Binary files a/inventory/__pycache__/urls.cpython-311.pyc and b/inventory/__pycache__/urls.cpython-311.pyc differ diff --git a/inventory/__pycache__/utils.cpython-311.pyc b/inventory/__pycache__/utils.cpython-311.pyc index 876655e8..485c0d0a 100644 Binary files a/inventory/__pycache__/utils.cpython-311.pyc and b/inventory/__pycache__/utils.cpython-311.pyc differ diff --git a/inventory/__pycache__/views.cpython-311.pyc b/inventory/__pycache__/views.cpython-311.pyc index b00dc5c3..b7129e6d 100644 Binary files a/inventory/__pycache__/views.cpython-311.pyc and b/inventory/__pycache__/views.cpython-311.pyc differ diff --git a/inventory/management/__pycache__/__init__.cpython-311.pyc b/inventory/management/__pycache__/__init__.cpython-311.pyc index 98179969..f8712ed7 100644 Binary files a/inventory/management/__pycache__/__init__.cpython-311.pyc and b/inventory/management/__pycache__/__init__.cpython-311.pyc differ diff --git a/inventory/migrations/0001_initial.py b/inventory/migrations/0001_initial.py index 4d9e2676..e76d84e3 100644 --- a/inventory/migrations/0001_initial.py +++ b/inventory/migrations/0001_initial.py @@ -1,5 +1,6 @@ -# Generated by Django 5.1.5 on 2025-01-30 11:28 +# Generated by Django 5.1.6 on 2025-03-06 01:43 +import datetime import django.db.models.deletion import inventory.mixins import inventory.models @@ -14,8 +15,12 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('appointment', '__first__'), + ('auth', '0012_alter_user_first_name_max_length'), ('contenttypes', '0002_remove_content_type_name'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL), + migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), migrations.swappable_dependency(settings.DJANGO_LEDGER_ENTITY_MODEL), migrations.swappable_dependency(settings.DJANGO_LEDGER_ESTIMATE_MODEL), migrations.swappable_dependency(settings.DJANGO_LEDGER_INVOICE_MODEL), @@ -94,25 +99,6 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'payments', }, ), - migrations.CreateModel( - name='SubscriptionPlan', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='Name of the subscription plan', max_length=100, unique=True)), - ('description', models.TextField()), - ('price', models.DecimalField(decimal_places=2, max_digits=10)), - ('max_users', models.PositiveIntegerField(default=1, help_text='Maximum number of users allowed')), - ('max_inventory_size', models.PositiveIntegerField(default=50, help_text='Maximum number of cars in inventory')), - ('support_level', models.CharField(choices=[('basic', 'Basic Support'), ('priority', 'Priority Support'), ('dedicated', 'Dedicated Support')], default='basic', help_text='Level of support provided', max_length=50)), - ('custom_features', models.JSONField(blank=True, help_text='Additional features specific to this plan', null=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ], - options={ - 'verbose_name': 'Subscription Plan', - 'verbose_name_plural': 'Subscription Plans', - }, - ), migrations.CreateModel( name='VatRate', fields=[ @@ -122,23 +108,6 @@ class Migration(migrations.Migration): ('created_at', models.DateTimeField(auto_now_add=True)), ], ), - 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'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('add_car', 'Add Car'), ('reserve_car', 'Reserve 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.CASCADE, to='contenttypes.contenttype')), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Activity', - 'verbose_name_plural': 'Activities', - }, - ), migrations.CreateModel( name='AdditionalServices', fields=[ @@ -168,6 +137,7 @@ class Migration(migrations.Migration): ('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')), ('vendor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to=settings.DJANGO_LEDGER_VENDOR_MODEL, verbose_name='Vendor')), ('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')), ], @@ -242,10 +212,10 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('plate_number', models.IntegerField(verbose_name='Plate Number')), ('text1', models.CharField(max_length=1, verbose_name='Text 1')), - ('text2', models.CharField(max_length=1, verbose_name='Text 2')), - ('text3', models.CharField(max_length=1, verbose_name='Text 3')), + ('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.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), + ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), ], options={ 'verbose_name': 'Registration', @@ -362,6 +332,15 @@ class Migration(migrations.Migration): ('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='')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer')), + ], + ), migrations.CreateModel( name='Customer', fields=[ @@ -378,6 +357,7 @@ class Migration(migrations.Migration): ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('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={ @@ -432,19 +412,78 @@ class Migration(migrations.Migration): 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'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('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=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('bill_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('bill_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('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=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('invoice_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('invoice_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ], + ), + migrations.CreateModel( + name='Email', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('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')), + ('lead_type', models.CharField(choices=[('customer', 'Customer'), ('organization', 'Organization')], default='customer', max_length=50, verbose_name='Lead Type')), ('year', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Year')), ('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')), - ('city', models.CharField(max_length=50, verbose_name='City')), + ('crn', models.CharField(blank=True, max_length=10, null=True, unique=True, verbose_name='Commercial Registration Number')), + ('vrn', models.CharField(blank=True, max_length=15, null=True, unique=True, verbose_name='VAT Registration Number')), + ('address', models.CharField(max_length=50, verbose_name='address')), ('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10, verbose_name='Priority')), - ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status')), + ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status')), ('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.customer')), + ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)), ('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')), @@ -554,6 +593,26 @@ class Migration(migrations.Migration): 'ordering': ['-created'], }, ), + 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=settings.DJANGO_LEDGER_CUSTOMER_MODEL)), + ('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=[ @@ -561,11 +620,11 @@ class Migration(migrations.Migration): ('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=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales'), ('coordinator', 'Coordinator'), ('receptionist', 'Receptionist'), ('agent', 'Agent')], max_length=255, verbose_name='Staff Type')), + ('staff_type', models.CharField(choices=[('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='inventory.dealer')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to=settings.AUTH_USER_MODEL)), + ('staff_member', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='appointment.staffmember')), ], options={ 'verbose_name': 'Staff', @@ -582,15 +641,17 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('stage', models.CharField(choices=[('prospect', 'Prospect'), ('proposal', 'Proposal'), ('negotiation', 'Negotiation'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost')], max_length=20, verbose_name='Stage')), - ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], default='new', max_length=20, verbose_name='Status')), + ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], default='new', max_length=20, verbose_name='Status')), ('probability', models.PositiveIntegerField(validators=[inventory.models.validate_probability])), - ('closing_date', models.DateField(verbose_name='Closing Date')), + ('closing_date', models.DateField(blank=True, null=True, verbose_name='Closing Date')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), ('closed', models.BooleanField(default=False, verbose_name='Closed')), ('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)), ('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=settings.DJANGO_LEDGER_ESTIMATE_MODEL)), + ('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead')), ('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner')), ], options={ @@ -602,8 +663,8 @@ class Migration(migrations.Migration): name='LeadStatusHistory', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('old_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')), - ('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status')), + ('old_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')), + ('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], 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')), @@ -618,40 +679,6 @@ class Migration(migrations.Migration): 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='Subscription', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('start_date', models.DateField(help_text='Date when the subscription starts')), - ('end_date', models.DateField(help_text='Date when the subscription ends')), - ('is_active', models.BooleanField(default=True)), - ('billing_cycle', models.CharField(choices=[('monthly', 'Monthly'), ('annual', 'Annual')], default='monthly', help_text='Billing cycle for the subscription', max_length=10)), - ('last_payment_date', models.DateField(blank=True, help_text='Date of the last payment made', null=True)), - ('next_payment_date', models.DateField(blank=True, help_text='Date of the next payment due', null=True)), - ('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subscriptions', to='inventory.subscriptionplan')), - ], - options={ - 'verbose_name': 'Subscription', - 'verbose_name_plural': 'Subscriptions', - }, - ), - migrations.CreateModel( - name='SubscriptionUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.subscription')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Subscription User', - 'verbose_name_plural': 'Subscription Users', - }, - ), - migrations.AddField( - model_name='subscription', - name='users', - field=models.ManyToManyField(through='inventory.SubscriptionUser', to=settings.AUTH_USER_MODEL), - ), migrations.CreateModel( name='UserActivityLog', fields=[ @@ -704,11 +731,23 @@ class Migration(migrations.Migration): '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.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.car')), + ('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')), ], diff --git a/inventory/migrations/0002_alter_carregistration_car.py b/inventory/migrations/0002_alter_carregistration_car.py deleted file mode 100644 index fc0584b0..00000000 --- a/inventory/migrations/0002_alter_carregistration_car.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.5 on 2025-02-04 04:37 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='carregistration', - name='car', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car'), - ), - ] diff --git a/inventory/migrations/0002_alter_lead_customer.py b/inventory/migrations/0002_alter_lead_customer.py deleted file mode 100644 index 03bc8b02..00000000 --- a/inventory/migrations/0002_alter_lead_customer.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-04 09:34 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ('inventory', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='lead', - name='customer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ), - ] diff --git a/inventory/migrations/0003_lead_email_lead_first_name_lead_last_name_and_more.py b/inventory/migrations/0003_lead_email_lead_first_name_lead_last_name_and_more.py deleted file mode 100644 index b0548b08..00000000 --- a/inventory/migrations/0003_lead_email_lead_first_name_lead_last_name_and_more.py +++ /dev/null @@ -1,46 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-04 11:38 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import phonenumber_field.modelfields - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ('inventory', '0002_alter_lead_customer'), - ] - - operations = [ - migrations.AddField( - model_name='lead', - name='email', - field=models.EmailField(default='x@tenhal.sa', max_length=254, verbose_name='Email'), - preserve_default=False, - ), - migrations.AddField( - model_name='lead', - name='first_name', - field=models.CharField(default='test', max_length=50, verbose_name='First Name'), - preserve_default=False, - ), - migrations.AddField( - model_name='lead', - name='last_name', - field=models.CharField(default='test', max_length=50, verbose_name='Last Name'), - preserve_default=False, - ), - migrations.AddField( - model_name='lead', - name='phone_number', - field=phonenumber_field.modelfields.PhoneNumberField(default='056523656', max_length=128, region='SA', verbose_name='Phone Number'), - preserve_default=False, - ), - migrations.AlterField( - model_name='lead', - name='customer', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ), - ] diff --git a/inventory/migrations/0004_remove_lead_city_lead_address.py b/inventory/migrations/0004_remove_lead_city_lead_address.py deleted file mode 100644 index 47e4a975..00000000 --- a/inventory/migrations/0004_remove_lead_city_lead_address.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-04 14:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0003_lead_email_lead_first_name_lead_last_name_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='lead', - name='city', - ), - migrations.AddField( - model_name='lead', - name='address', - field=models.CharField(default='', max_length=50, verbose_name='address'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0005_schedule.py b/inventory/migrations/0005_schedule.py deleted file mode 100644 index 8701e470..00000000 --- a/inventory/migrations/0005_schedule.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-04 15:15 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ('inventory', '0004_remove_lead_city_lead_address'), - ] - - operations = [ - migrations.CreateModel( - name='Schedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('purpose', models.CharField(max_length=200)), - ('scheduled_at', models.DateTimeField()), - ('notes', models.TextField(blank=True, null=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)), - ('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='inventory.staff')), - ], - options={ - 'ordering': ['-scheduled_at'], - }, - ), - ] diff --git a/inventory/migrations/0006_alter_schedule_purpose.py b/inventory/migrations/0006_alter_schedule_purpose.py deleted file mode 100644 index 2b834016..00000000 --- a/inventory/migrations/0006_alter_schedule_purpose.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-04 15:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0005_schedule'), - ] - - operations = [ - migrations.AlterField( - model_name='schedule', - name='purpose', - field=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), - ), - ] diff --git a/inventory/migrations/0007_schedule_scheduled_type.py b/inventory/migrations/0007_schedule_scheduled_type.py deleted file mode 100644 index ceaf14f9..00000000 --- a/inventory/migrations/0007_schedule_scheduled_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-04 15:36 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0006_alter_schedule_purpose'), - ] - - operations = [ - migrations.AddField( - model_name='schedule', - name='scheduled_type', - field=models.CharField(choices=[('Call', 'Call'), ('Meeting', 'Meeting'), ('Email', 'Email')], default='Call', max_length=200), - ), - ] diff --git a/inventory/migrations/0008_schedule_status.py b/inventory/migrations/0008_schedule_status.py deleted file mode 100644 index 6d1d25ac..00000000 --- a/inventory/migrations/0008_schedule_status.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-05 09:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0007_schedule_scheduled_type'), - ] - - operations = [ - migrations.AddField( - model_name='schedule', - name='status', - field=models.CharField(choices=[('Scheduled', 'Scheduled'), ('Completed', 'Completed'), ('Canceled', 'Canceled')], default='Scheduled', max_length=200), - ), - ] diff --git a/inventory/migrations/0009_alter_opportunity_customer.py b/inventory/migrations/0009_alter_opportunity_customer.py deleted file mode 100644 index a0497c71..00000000 --- a/inventory/migrations/0009_alter_opportunity_customer.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-05 10:00 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ('inventory', '0008_schedule_status'), - ] - - operations = [ - migrations.AlterField( - model_name='opportunity', - name='customer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ), - ] diff --git a/inventory/migrations/0010_remove_lead_id_car_make_remove_lead_id_car_model_and_more.py b/inventory/migrations/0010_remove_lead_id_car_make_remove_lead_id_car_model_and_more.py deleted file mode 100644 index 71bbe9b0..00000000 --- a/inventory/migrations/0010_remove_lead_id_car_make_remove_lead_id_car_model_and_more.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-05 10:05 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0009_alter_opportunity_customer'), - ] - - operations = [ - migrations.RemoveField( - model_name='lead', - name='id_car_make', - ), - migrations.RemoveField( - model_name='lead', - name='id_car_model', - ), - migrations.AddField( - model_name='lead', - name='car', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.car', verbose_name='Car'), - ), - ] diff --git a/inventory/migrations/0011_remove_lead_year_alter_schedule_customer.py b/inventory/migrations/0011_remove_lead_year_alter_schedule_customer.py deleted file mode 100644 index c317e165..00000000 --- a/inventory/migrations/0011_remove_lead_year_alter_schedule_customer.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-05 10:17 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ('inventory', '0010_remove_lead_id_car_make_remove_lead_id_car_model_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='lead', - name='year', - ), - migrations.AlterField( - model_name='schedule', - name='customer', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ), - ] diff --git a/inventory/migrations/0012_merge_20250206_1151.py b/inventory/migrations/0012_merge_20250206_1151.py deleted file mode 100644 index 5034543c..00000000 --- a/inventory/migrations/0012_merge_20250206_1151.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 5.1.5 on 2025-02-06 08:51 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0002_alter_carregistration_car'), - ('inventory', '0011_remove_lead_year_alter_schedule_customer'), - ] - - operations = [ - ] diff --git a/inventory/migrations/0012_merge_20250206_1308.py b/inventory/migrations/0012_merge_20250206_1308.py deleted file mode 100644 index f185be66..00000000 --- a/inventory/migrations/0012_merge_20250206_1308.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-06 10:08 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0002_alter_carregistration_car'), - ('inventory', '0011_remove_lead_year_alter_schedule_customer'), - ] - - operations = [ - ] diff --git a/inventory/migrations/0013_alter_carregistration_text2_and_more.py b/inventory/migrations/0013_alter_carregistration_text2_and_more.py deleted file mode 100644 index 98bb0ab7..00000000 --- a/inventory/migrations/0013_alter_carregistration_text2_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.1.5 on 2025-02-07 01:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0012_merge_20250206_1151'), - ] - - operations = [ - migrations.AlterField( - model_name='carregistration', - name='text2', - field=models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 2'), - ), - migrations.AlterField( - model_name='carregistration', - name='text3', - field=models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 3'), - ), - ] diff --git a/inventory/migrations/0014_remove_lead_car_lead_id_car_make_lead_id_car_model_and_more.py b/inventory/migrations/0014_remove_lead_car_lead_id_car_make_lead_id_car_model_and_more.py deleted file mode 100644 index fece8a8e..00000000 --- a/inventory/migrations/0014_remove_lead_car_lead_id_car_make_lead_id_car_model_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-06 12:07 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0013_alter_carregistration_text2_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='lead', - name='car', - ), - migrations.AddField( - model_name='lead', - name='id_car_make', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make'), - ), - migrations.AddField( - model_name='lead', - name='id_car_model', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'), - ), - migrations.AddField( - model_name='lead', - name='year', - field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Year'), - ), - ] diff --git a/inventory/migrations/0015_merge_20250209_1116.py b/inventory/migrations/0015_merge_20250209_1116.py deleted file mode 100644 index a5a709bd..00000000 --- a/inventory/migrations/0015_merge_20250209_1116.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-09 08:16 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0012_merge_20250206_1308'), - ('inventory', '0014_remove_lead_car_lead_id_car_make_lead_id_car_model_and_more'), - ] - - operations = [ - ] diff --git a/inventory/migrations/0016_schedule_duration.py b/inventory/migrations/0016_schedule_duration.py deleted file mode 100644 index 82536c79..00000000 --- a/inventory/migrations/0016_schedule_duration.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-09 08:23 - -import datetime -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0015_merge_20250209_1116'), - ] - - operations = [ - migrations.AddField( - model_name='schedule', - name='duration', - field=models.DurationField(default=datetime.timedelta(seconds=300)), - ), - ] diff --git a/inventory/migrations/0017_car_hash.py b/inventory/migrations/0017_car_hash.py deleted file mode 100644 index 88457273..00000000 --- a/inventory/migrations/0017_car_hash.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-09 11:36 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0016_schedule_duration'), - ] - - operations = [ - migrations.AddField( - model_name='car', - name='hash', - field=models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash'), - ), - ] diff --git a/inventory/migrations/0018_customer_user.py b/inventory/migrations/0018_customer_user.py deleted file mode 100644 index c33f8ff6..00000000 --- a/inventory/migrations/0018_customer_user.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.1.5 on 2025-02-11 00:23 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0017_car_hash'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='customer', - name='user', - field=models.OneToOneField(default=4, on_delete=django.db.models.deletion.CASCADE, related_name='customer_profile', to=settings.AUTH_USER_MODEL), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0019_opportunity_lead.py b/inventory/migrations/0019_opportunity_lead.py deleted file mode 100644 index f7e643d8..00000000 --- a/inventory/migrations/0019_opportunity_lead.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-12 10:26 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0018_customer_user'), - ] - - operations = [ - migrations.AddField( - model_name='opportunity', - name='lead', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='inventory.lead'), - ), - ] diff --git a/inventory/migrations/0020_alter_opportunity_closing_date.py b/inventory/migrations/0020_alter_opportunity_closing_date.py deleted file mode 100644 index 3b6324fd..00000000 --- a/inventory/migrations/0020_alter_opportunity_closing_date.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-12 10:32 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0019_opportunity_lead'), - ] - - operations = [ - migrations.AlterField( - model_name='opportunity', - name='closing_date', - field=models.DateField(blank=True, null=True, verbose_name='Closing Date'), - ), - ] diff --git a/inventory/migrations/0021_alter_opportunity_lead.py b/inventory/migrations/0021_alter_opportunity_lead.py deleted file mode 100644 index 16cd0fb7..00000000 --- a/inventory/migrations/0021_alter_opportunity_lead.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-12 10:37 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0020_alter_opportunity_closing_date'), - ] - - operations = [ - migrations.AlterField( - model_name='opportunity', - name='lead', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead'), - ), - ] diff --git a/inventory/migrations/0022_opportunity_estimate.py b/inventory/migrations/0022_opportunity_estimate.py deleted file mode 100644 index f5ba3a9a..00000000 --- a/inventory/migrations/0022_opportunity_estimate.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-12 14:09 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_ESTIMATE_MODEL), - ('inventory', '0021_alter_opportunity_lead'), - ] - - operations = [ - migrations.AddField( - model_name='opportunity', - name='estimate', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to=settings.DJANGO_LEDGER_ESTIMATE_MODEL), - ), - ] diff --git a/inventory/migrations/0023_email.py b/inventory/migrations/0023_email.py deleted file mode 100644 index 500a86b4..00000000 --- a/inventory/migrations/0023_email.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-13 09:01 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0002_remove_content_type_name'), - ('inventory', '0022_opportunity_estimate'), - ] - - operations = [ - migrations.CreateModel( - name='Email', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.PositiveIntegerField()), - ('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')), - ('body', models.TextField(blank=True, null=True, verbose_name='Body')), - ('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', - }, - ), - ] diff --git a/inventory/migrations/0024_remove_email_body_email_message.py b/inventory/migrations/0024_remove_email_body_email_message.py deleted file mode 100644 index 0005c07c..00000000 --- a/inventory/migrations/0024_remove_email_body_email_message.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-13 09:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0023_email'), - ] - - operations = [ - migrations.RemoveField( - model_name='email', - name='body', - ), - migrations.AddField( - model_name='email', - name='message', - field=models.TextField(blank=True, null=True, verbose_name='Message'), - ), - ] diff --git a/inventory/migrations/0025_email_status.py b/inventory/migrations/0025_email_status.py deleted file mode 100644 index baca6d3c..00000000 --- a/inventory/migrations/0025_email_status.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-13 09:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0024_remove_email_body_email_message'), - ] - - operations = [ - migrations.AddField( - model_name='email', - name='status', - field=models.CharField(choices=[('SENT', 'Sent'), ('FAILED', 'Failed'), ('DELIVERED', 'Delivered'), ('OPEN', 'Open'), ('DRAFT', 'Draft')], default='OPEN', max_length=20, verbose_name='Status'), - ), - ] diff --git a/inventory/migrations/0026_carhistory.py b/inventory/migrations/0026_carhistory.py deleted file mode 100644 index 21e89fa1..00000000 --- a/inventory/migrations/0026_carhistory.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-17 08:54 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0025_email_status'), - ] - - operations = [ - migrations.CreateModel( - name='CarHistory', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('event_date', models.DateField()), - ('event_type', models.CharField(choices=[('PURCHASE', 'Purchase'), ('SALE', 'Sale'), ('TRANSFER', 'Transfer'), ('ACCIDENT', 'Accident'), ('MAINTENANCE', 'Maintenance'), ('SERVICE', 'Service'), ('OTHER', 'Other')], max_length=50)), - ('description', models.TextField(blank=True, null=True)), - ('mileage', models.IntegerField(blank=True, null=True)), - ('cost', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), - ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='history', to='inventory.car')), - ], - options={ - 'ordering': ['-event_date'], - }, - ), - ] diff --git a/inventory/migrations/0027_carhistory_dealer.py b/inventory/migrations/0027_carhistory_dealer.py deleted file mode 100644 index 8d5bdb6b..00000000 --- a/inventory/migrations/0027_carhistory_dealer.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-17 08:57 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0026_carhistory'), - ] - - operations = [ - migrations.AddField( - model_name='carhistory', - name='dealer', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='history', to='inventory.dealer'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0028_carhistory_additional_info.py b/inventory/migrations/0028_carhistory_additional_info.py deleted file mode 100644 index 5c54c367..00000000 --- a/inventory/migrations/0028_carhistory_additional_info.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-17 09:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0027_carhistory_dealer'), - ] - - operations = [ - migrations.AddField( - model_name='carhistory', - name='additional_info', - field=models.JSONField(blank=True, default=dict, null=True, verbose_name='Car History Additional Info'), - ), - ] diff --git a/inventory/migrations/0029_remove_carhistory_cost_remove_carhistory_mileage.py b/inventory/migrations/0029_remove_carhistory_cost_remove_carhistory_mileage.py deleted file mode 100644 index 131fea9b..00000000 --- a/inventory/migrations/0029_remove_carhistory_cost_remove_carhistory_mileage.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-17 09:03 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0028_carhistory_additional_info'), - ] - - operations = [ - migrations.RemoveField( - model_name='carhistory', - name='cost', - ), - migrations.RemoveField( - model_name='carhistory', - name='mileage', - ), - ] diff --git a/inventory/migrations/0030_alter_activity_activity_type_delete_carhistory.py b/inventory/migrations/0030_alter_activity_activity_type_delete_carhistory.py deleted file mode 100644 index 6e731bf0..00000000 --- a/inventory/migrations/0030_alter_activity_activity_type_delete_carhistory.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-17 09:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0029_remove_carhistory_cost_remove_carhistory_mileage'), - ] - - operations = [ - migrations.AlterField( - model_name='activity', - name='activity_type', - field=models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('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'), - ), - migrations.DeleteModel( - name='CarHistory', - ), - ] diff --git a/inventory/migrations/0031_activity_dealer_alter_activity_content_type_and_more.py b/inventory/migrations/0031_activity_dealer_alter_activity_content_type_and_more.py deleted file mode 100644 index 9e7cd356..00000000 --- a/inventory/migrations/0031_activity_dealer_alter_activity_content_type_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 5.1.5 on 2025-02-17 14:05 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('inventory', '0030_alter_activity_activity_type_delete_carhistory'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='activity', - name='dealer', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='inventory.dealer'), - preserve_default=False, - ), - migrations.AlterField( - model_name='activity', - name='content_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.contenttype'), - ), - migrations.AlterField( - model_name='activity', - name='created_by', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created_by', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/inventory/migrations/0032_alter_carcolors_car.py b/inventory/migrations/0032_alter_carcolors_car.py deleted file mode 100644 index d7d0d5a9..00000000 --- a/inventory/migrations/0032_alter_carcolors_car.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.6 on 2025-02-17 17:40 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0031_activity_dealer_alter_activity_content_type_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='carcolors', - name='car', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.car'), - ), - ] diff --git a/inventory/migrations/0033_remove_staff_user_staff_staff_member_and_more.py b/inventory/migrations/0033_remove_staff_user_staff_staff_member_and_more.py deleted file mode 100644 index 79831b81..00000000 --- a/inventory/migrations/0033_remove_staff_user_staff_staff_member_and_more.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 5.1.6 on 2025-02-19 05:25 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - # ('appointment', '0002_alter_workinghours_options'), - ('inventory', '0032_alter_carcolors_car'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.RemoveField( - model_name='staff', - name='user', - ), - migrations.AddField( - model_name='staff', - name='staff_member', - field=models.OneToOneField(default=5, on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='appointment.staffmember'), - preserve_default=False, - ), - migrations.AlterField( - model_name='schedule', - name='scheduled_by', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/inventory/migrations/0034_remove_subscription_plan_remove_subscription_users_and_more.py b/inventory/migrations/0034_remove_subscription_plan_remove_subscription_users_and_more.py deleted file mode 100644 index 0ab94aee..00000000 --- a/inventory/migrations/0034_remove_subscription_plan_remove_subscription_users_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 5.1.6 on 2025-02-20 01:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0033_remove_staff_user_staff_staff_member_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='subscription', - name='plan', - ), - migrations.RemoveField( - model_name='subscription', - name='users', - ), - migrations.RemoveField( - model_name='subscriptionuser', - name='subscription', - ), - migrations.RemoveField( - model_name='subscriptionuser', - name='user', - ), - migrations.DeleteModel( - name='SubscriptionPlan', - ), - migrations.DeleteModel( - name='Subscription', - ), - migrations.DeleteModel( - name='SubscriptionUser', - ), - ] diff --git a/inventory/migrations/0038_customgroup.py b/inventory/migrations/0038_customgroup.py deleted file mode 100644 index 2b632508..00000000 --- a/inventory/migrations/0038_customgroup.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-20 08:16 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - # ('inventory', '0037_alter_schedule_scheduled_type'), - ('inventory', '0034_remove_subscription_plan_remove_subscription_users_and_more'), - ] - - operations = [ - 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)), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer')), - ('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='')), - ], - ), - ] diff --git a/inventory/migrations/0039_alter_customgroup_dealer.py b/inventory/migrations/0039_alter_customgroup_dealer.py deleted file mode 100644 index c3bfae8f..00000000 --- a/inventory/migrations/0039_alter_customgroup_dealer.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-20 08:17 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0038_customgroup'), - ] - - operations = [ - migrations.AlterField( - model_name='customgroup', - name='dealer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer'), - ), - ] diff --git a/inventory/migrations/0040_usersettings.py b/inventory/migrations/0040_usersettings.py deleted file mode 100644 index 84c925d9..00000000 --- a/inventory/migrations/0040_usersettings.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-23 14:31 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('inventory', '0039_alter_customgroup_dealer'), - ] - - operations = [ - migrations.CreateModel( - name='UserSettings', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('language', models.CharField(choices=[('en', 'English'), ('ar', 'Arabic')], default='ar', max_length=20)), - ('theme', models.CharField(choices=[('default', 'Default'), ('dark', 'Dark')], default='default', max_length=20)), - ('additional_info', models.JSONField(default=dict)), - ('bill_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_ca', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('bill_payable_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_payable', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('bill_prepaid_expense_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid_expense', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('invoice_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_ca', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('invoice_payable_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_payable', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('invoice_prepaid_expense_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid_expense', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/inventory/migrations/0041_alter_usersettings_additional_info.py b/inventory/migrations/0041_alter_usersettings_additional_info.py deleted file mode 100644 index e2dd6996..00000000 --- a/inventory/migrations/0041_alter_usersettings_additional_info.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-23 14:56 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0040_usersettings'), - ] - - operations = [ - migrations.AlterField( - model_name='usersettings', - name='additional_info', - field=models.JSONField(blank=True, default=dict, null=True), - ), - ] diff --git a/inventory/migrations/0042_alter_usersettings_user.py b/inventory/migrations/0042_alter_usersettings_user.py deleted file mode 100644 index 394cfedf..00000000 --- a/inventory/migrations/0042_alter_usersettings_user.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-23 15:34 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('inventory', '0041_alter_usersettings_additional_info'), - ] - - operations = [ - migrations.AlterField( - model_name='usersettings', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/inventory/migrations/0043_alter_usersettings_user.py b/inventory/migrations/0043_alter_usersettings_user.py deleted file mode 100644 index 2417cec3..00000000 --- a/inventory/migrations/0043_alter_usersettings_user.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-23 16:23 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('inventory', '0042_alter_usersettings_user'), - ] - - operations = [ - migrations.AlterField( - model_name='usersettings', - name='user', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='settings', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/inventory/migrations/0044_dealersettings_delete_usersettings.py b/inventory/migrations/0044_dealersettings_delete_usersettings.py deleted file mode 100644 index db9c550b..00000000 --- a/inventory/migrations/0044_dealersettings_delete_usersettings.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-23 16:31 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ('inventory', '0043_alter_usersettings_user'), - ] - - operations = [ - 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_ca', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('bill_payable_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_payable', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('bill_prepaid_expense_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid_expense', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('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_ca', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('invoice_payable_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_payable', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('invoice_prepaid_expense_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid_expense', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ], - ), - migrations.DeleteModel( - name='UserSettings', - ), - ] diff --git a/inventory/migrations/0045_remove_dealersettings_invoice_payable_account_and_more.py b/inventory/migrations/0045_remove_dealersettings_invoice_payable_account_and_more.py deleted file mode 100644 index c1371ad8..00000000 --- a/inventory/migrations/0045_remove_dealersettings_invoice_payable_account_and_more.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-24 09:01 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ('inventory', '0044_dealersettings_delete_usersettings'), - ] - - operations = [ - migrations.RemoveField( - model_name='dealersettings', - name='invoice_payable_account', - ), - migrations.RemoveField( - model_name='dealersettings', - name='invoice_prepaid_expense_account', - ), - migrations.AddField( - model_name='dealersettings', - name='invoice_prepaid_account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ), - migrations.AddField( - model_name='dealersettings', - name='invoice_unearned_account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ), - migrations.AlterField( - model_name='dealersettings', - name='invoice_cash_account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ), - ] diff --git a/inventory/migrations/0046_remove_dealersettings_invoice_prepaid_account_and_more.py b/inventory/migrations/0046_remove_dealersettings_invoice_prepaid_account_and_more.py deleted file mode 100644 index 34720538..00000000 --- a/inventory/migrations/0046_remove_dealersettings_invoice_prepaid_account_and_more.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-24 09:10 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ('inventory', '0045_remove_dealersettings_invoice_payable_account_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='dealersettings', - name='invoice_prepaid_account', - ), - migrations.AddField( - model_name='dealersettings', - name='invoice_recivable_account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_recivable', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ), - ] diff --git a/inventory/migrations/0047_remove_dealersettings_invoice_recivable_account_and_more.py b/inventory/migrations/0047_remove_dealersettings_invoice_recivable_account_and_more.py deleted file mode 100644 index 1d85b218..00000000 --- a/inventory/migrations/0047_remove_dealersettings_invoice_recivable_account_and_more.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-24 09:11 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ('inventory', '0046_remove_dealersettings_invoice_prepaid_account_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='dealersettings', - name='invoice_recivable_account', - ), - migrations.AddField( - model_name='dealersettings', - name='invoice_prepaid_account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ), - ] diff --git a/inventory/migrations/0048_remove_dealersettings_bill_payable_account_and_more.py b/inventory/migrations/0048_remove_dealersettings_bill_payable_account_and_more.py deleted file mode 100644 index 8ec7bf14..00000000 --- a/inventory/migrations/0048_remove_dealersettings_bill_payable_account_and_more.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-24 09:14 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ('inventory', '0047_remove_dealersettings_invoice_recivable_account_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='dealersettings', - name='bill_payable_account', - ), - migrations.RemoveField( - model_name='dealersettings', - name='bill_prepaid_expense_account', - ), - migrations.AddField( - model_name='dealersettings', - name='bill_prepaid_account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ), - migrations.AddField( - model_name='dealersettings', - name='bill_unearned_account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ), - migrations.AlterField( - model_name='dealersettings', - name='bill_cash_account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL), - ), - ] diff --git a/inventory/migrations/0049_alter_lead_status_alter_leadstatushistory_new_status_and_more.py b/inventory/migrations/0049_alter_lead_status_alter_leadstatushistory_new_status_and_more.py deleted file mode 100644 index b611f7cf..00000000 --- a/inventory/migrations/0049_alter_lead_status_alter_leadstatushistory_new_status_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-26 08:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0048_remove_dealersettings_bill_payable_account_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='lead', - name='status', - field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status'), - ), - migrations.AlterField( - model_name='leadstatushistory', - name='new_status', - field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status'), - ), - migrations.AlterField( - model_name='leadstatushistory', - name='old_status', - field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status'), - ), - migrations.AlterField( - model_name='opportunity', - name='status', - field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], default='new', max_length=20, verbose_name='Status'), - ), - ] diff --git a/inventory/migrations/0049_carreservation_reserved_for.py b/inventory/migrations/0049_carreservation_reserved_for.py deleted file mode 100644 index d4086b3c..00000000 --- a/inventory/migrations/0049_carreservation_reserved_for.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.1.6 on 2025-02-24 17:25 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0048_remove_dealersettings_bill_payable_account_and_more'), - migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='carreservation', - name='reserved_for', - field=models.ForeignKey(default='dd747dc3-39bc-411f-a17d-c930a50220fe', on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL, verbose_name='Reserved For'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0050_remove_carreservation_reserved_for.py b/inventory/migrations/0050_remove_carreservation_reserved_for.py deleted file mode 100644 index cfc42e5d..00000000 --- a/inventory/migrations/0050_remove_carreservation_reserved_for.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.6 on 2025-02-25 01:05 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0049_carreservation_reserved_for'), - ] - - operations = [ - migrations.RemoveField( - model_name='carreservation', - name='reserved_for', - ), - ] diff --git a/inventory/migrations/0051_merge_20250226_1654.py b/inventory/migrations/0051_merge_20250226_1654.py deleted file mode 100644 index 53d97437..00000000 --- a/inventory/migrations/0051_merge_20250226_1654.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-26 13:54 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0049_alter_lead_status_alter_leadstatushistory_new_status_and_more'), - ('inventory', '0050_remove_carreservation_reserved_for'), - ] - - operations = [ - ] diff --git a/inventory/migrations/0052_lead_lead_type.py b/inventory/migrations/0052_lead_lead_type.py deleted file mode 100644 index e0d79b3c..00000000 --- a/inventory/migrations/0052_lead_lead_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-27 15:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0051_merge_20250226_1654'), - ] - - operations = [ - migrations.AddField( - model_name='lead', - name='lead_type', - field=models.CharField(choices=[('customer', 'Customer'), ('organization', 'Organization')], default='customer', max_length=50, verbose_name='Lead Type'), - ), - ] diff --git a/inventory/migrations/0053_lead_crn_lead_vrn.py b/inventory/migrations/0053_lead_crn_lead_vrn.py deleted file mode 100644 index baed021a..00000000 --- a/inventory/migrations/0053_lead_crn_lead_vrn.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.17 on 2025-03-01 21:22 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0052_lead_lead_type'), - ] - - operations = [ - migrations.AddField( - model_name='lead', - name='crn', - field=models.CharField(blank=True, max_length=10, null=True, unique=True, verbose_name='Commercial Registration Number'), - ), - migrations.AddField( - model_name='lead', - name='vrn', - field=models.CharField(blank=True, max_length=15, null=True, unique=True, verbose_name='VAT Registration Number'), - ), - ] diff --git a/inventory/migrations/0054_alter_staff_staff_type.py b/inventory/migrations/0054_alter_staff_staff_type.py deleted file mode 100644 index 1b6c0ae1..00000000 --- a/inventory/migrations/0054_alter_staff_staff_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-03-04 01:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0053_lead_crn_lead_vrn'), - ] - - operations = [ - migrations.AlterField( - model_name='staff', - name='staff_type', - field=models.CharField(choices=[('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type'), - ), - ] diff --git a/inventory/migrations/0054_dealersmake.py b/inventory/migrations/0054_dealersmake.py deleted file mode 100644 index 669ed228..00000000 --- a/inventory/migrations/0054_dealersmake.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-03 16:59 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0053_lead_crn_lead_vrn'), - ] - - operations = [ - 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')}, - }, - ), - ] diff --git a/inventory/migrations/0055_merge_0054_alter_staff_staff_type_0054_dealersmake.py b/inventory/migrations/0055_merge_0054_alter_staff_staff_type_0054_dealersmake.py deleted file mode 100644 index 7c5c881e..00000000 --- a/inventory/migrations/0055_merge_0054_alter_staff_staff_type_0054_dealersmake.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.17 on 2025-03-04 21:15 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0054_alter_staff_staff_type'), - ('inventory', '0054_dealersmake'), - ] - - operations = [ - ] diff --git a/inventory/templatetags/__pycache__/custom_filters.cpython-311.pyc b/inventory/templatetags/__pycache__/custom_filters.cpython-311.pyc index 5eefe0ce..be937089 100644 Binary files a/inventory/templatetags/__pycache__/custom_filters.cpython-311.pyc and b/inventory/templatetags/__pycache__/custom_filters.cpython-311.pyc differ diff --git a/inventory/views.py b/inventory/views.py index c4a9452e..23f4af1c 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -210,6 +210,11 @@ def dealer_signup(request, *args, **kwargs): user = User.objects.create(username=email, email=email) user.set_password(password) user.save() + group = Group.objects.create(name=f'{user.pk}-Admin') + user.groups.add(group) + for perm in Permission.objects.filter(content_type__app_label__in=["inventory","django_ledger"]): + group.permissions.add(perm) + StaffMember.objects.create(user=user) models.Dealer.objects.create( user=user, @@ -1300,7 +1305,7 @@ class CustomerDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView) return context @login_required -def add_note_to_customer(request, customer_id): +def add_note_to_customer(request, customer_id): customer = get_object_or_404(CustomerModel, uuid=customer_id) if request.method == "POST": form = forms.NoteForm(request.POST) @@ -2721,7 +2726,7 @@ class UserActivityLogListView(LoginRequiredMixin,ListView): queryset = super().get_queryset() if "user" in self.request.GET: queryset = queryset.filter(user__email=self.request.GET["user"]) - return queryset + return queryset[:100] # will update later with better pagination # CRM RELATED VIEWS diff --git a/requirements.txt b/requirements.txt index e2801725..420449fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ certifi==2025.1.31 cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.4.1 -click==8.1.8 +click colorama==0.4.6 commonmark==0.9.1 contourpy==1.3.1 @@ -55,7 +55,7 @@ django-extensions==3.2.3 django-filter==25.1 django-formtools==2.5.1 django-import-export==4.3.5 -django-ledger==0.7.4.1 +django-ledger django-model-utils==5.0.0 django-money==3.5.3 django-next-url-mixin==0.4.0 @@ -89,7 +89,6 @@ et_xmlfile==2.0.0 Faker==36.1.1 filelock==3.17.0 fire==0.7.0 -Flask==3.1.0 fonttools==4.56.0 fpdf2==2.8.2 frozenlist==1.5.0 @@ -151,22 +150,16 @@ packaging==24.2 pandas==2.2.3 pango==0.0.1 pdfkit==1.0.0 -phonenumbers==8.13.55 -pillow==11.1.0 platformdirs==4.3.6 prometheus_client==0.21.1 propcache==0.2.1 protobuf==5.29.3 -psycopg==3.2.4 psycopg-binary==3.2.4 -psycopg-c==3.2.4 py-moneyed==3.0 PyAutoGUI==0.9.54 pyclipper==1.3.0.post6 pycodestyle==2.12.1 pycparser==2.22 -pydantic==2.10.6 -pydantic_core==2.29.0 pydotplus==2.0.2 pydyf==0.11.0 PyGetWindow==0.0.9 @@ -174,10 +167,6 @@ Pygments==2.19.1 PyJWT==2.10.1 pylint==3.3.4 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.1 pypdf==5.3.0 PyPDF2==3.0.1 @@ -198,8 +187,6 @@ pytweening==1.2.0 pytz==2025.1 pyvin==0.0.2 pywa==2.7.0 -pywhat==5.1.0 -pywhatkit==5.4 PyYAML==6.0.2 pyzbar==0.1.9 qrcode==8.0 @@ -229,7 +216,6 @@ sqlparse==0.5.3 stanza==1.10.1 stringzilla==3.11.3 suds==1.2.0 -swapper==1.4.0 sympy==1.13.1 tablib==3.8.0 termcolor==2.5.0 @@ -263,3 +249,5 @@ wsproto==1.2.0 xmlsec==1.3.14 yarl==1.18.3 zopfli==0.2.3.post1 +python-dotenv +psycopg2-binary \ No newline at end of file diff --git a/scripts/run.py b/scripts/run.py index 30c3b438..ca76ad5d 100644 --- a/scripts/run.py +++ b/scripts/run.py @@ -1,3 +1,5 @@ +import os +from dotenv import load_dotenv from django.contrib.auth.models import Permission from django.contrib.auth.models import Group from django_ledger.models.invoice import InvoiceModel @@ -17,6 +19,7 @@ import hashlib User = get_user_model() +load_dotenv(".env") def run(): # print(Service.objects.first().pk) # print(Appointment.objects.first().client) @@ -145,7 +148,8 @@ def run(): # print(Permission.objects.filter(codename__in=['view_car','view_carlocation','view_customcard','view_carcolors','view_cartransfer','view_estimatemodel','view_invoicemodel','view_saleorder'])) # print(Permission.objects.filter(codename__in=['view_estimatemodel','view_invoicemodel','view_saleorder'])) # CustomGroup.objects.filter(name='Accountant').last().set_default_permissions() - CustomGroup.objects.filter(name='Inventory').last().set_default_permissions() + # CustomGroup.objects.filter(name='Inventory').last().set_default_permissions() # EntityManagementModel.objects.create(entity=,user=) # print(Permission.objects.filter(codename__icontains='customermodel').first().codename) + print(os.getenv("DJANGO_ALLOWED_HOSTS")) diff --git a/staticfiles/admin/css/autocomplete.css b/staticfiles/admin/css/autocomplete.css index 69c94e73..7478c2c4 100644 --- a/staticfiles/admin/css/autocomplete.css +++ b/staticfiles/admin/css/autocomplete.css @@ -273,3 +273,7 @@ select.admin-autocomplete { display: block; padding: 6px; } + +.errors .select2-selection { + border: 1px solid var(--error-fg); +} diff --git a/staticfiles/admin/css/base.css b/staticfiles/admin/css/base.css index 93db7d06..ac283260 100644 --- a/staticfiles/admin/css/base.css +++ b/staticfiles/admin/css/base.css @@ -13,6 +13,7 @@ html[data-theme="light"], --body-fg: #333; --body-bg: #fff; --body-quiet-color: #666; + --body-medium-color: #444; --body-loud-color: #000; --header-color: #ffc; @@ -22,11 +23,11 @@ html[data-theme="light"], --breadcrumbs-fg: #c4dce8; --breadcrumbs-link-fg: var(--body-bg); - --breadcrumbs-bg: var(--primary); + --breadcrumbs-bg: #264b5d; --link-fg: #417893; --link-hover-color: #036; - --link-selected-fg: #5b80b2; + --link-selected-fg: var(--secondary); --hairline-color: #e8e8e8; --border-color: #ccc; @@ -42,10 +43,10 @@ html[data-theme="light"], --selected-row: #ffc; --button-fg: #fff; - --button-bg: var(--primary); - --button-hover-bg: #609ab6; - --default-button-bg: var(--secondary); - --default-button-hover-bg: #205067; + --button-bg: var(--secondary); + --button-hover-bg: #205067; + --default-button-bg: #205067; + --default-button-hover-bg: var(--secondary); --close-button-bg: #747474; --close-button-hover-bg: #333; --delete-button-bg: #ba2121; @@ -56,8 +57,6 @@ html[data-theme="light"], --object-tools-hover-bg: var(--close-button-hover-bg); --font-family-primary: - -apple-system, - BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, @@ -86,6 +85,8 @@ html[data-theme="light"], "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + + color-scheme: light; } html, body { @@ -149,7 +150,6 @@ h1 { margin: 0 0 20px; font-weight: 300; font-size: 1.25rem; - color: var(--body-quiet-color); } h2 { @@ -165,7 +165,7 @@ h2.subhead { h3 { font-size: 0.875rem; margin: .8em 0 .3em 0; - color: var(--body-quiet-color); + color: var(--body-medium-color); font-weight: bold; } @@ -173,6 +173,7 @@ h4 { font-size: 0.75rem; margin: 1em 0 .8em 0; padding-bottom: 3px; + color: var(--body-medium-color); } h5 { @@ -219,6 +220,10 @@ fieldset { border-top: 1px solid var(--hairline-color); } +details summary { + cursor: pointer; +} + blockquote { font-size: 0.6875rem; color: #777; @@ -315,7 +320,7 @@ td, th { } th { - font-weight: 600; + font-weight: 500; text-align: left; } @@ -336,7 +341,7 @@ tfoot td { } thead th.required { - color: var(--body-loud-color); + font-weight: bold; } tr.alt { @@ -484,8 +489,13 @@ textarea { vertical-align: top; } -input[type=text], input[type=password], input[type=email], input[type=url], -input[type=number], input[type=tel], textarea, select, .vTextField { +/* +Minifiers remove the default (text) "type" attribute from "input" HTML tags. +Add input:not([type]) to make the CSS stylesheet work the same. +*/ +input:not([type]), input[type=text], input[type=password], input[type=email], +input[type=url], input[type=number], input[type=tel], textarea, select, +.vTextField { border: 1px solid var(--border-color); border-radius: 4px; padding: 5px 6px; @@ -494,9 +504,13 @@ input[type=number], input[type=tel], textarea, select, .vTextField { background-color: var(--body-bg); } -input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, -input[type=url]:focus, input[type=number]:focus, input[type=tel]:focus, -textarea:focus, select:focus, .vTextField:focus { +/* +Minifiers remove the default (text) "type" attribute from "input" HTML tags. +Add input:not([type]) to make the CSS stylesheet work the same. +*/ +input:not([type]):focus, input[type=text]:focus, input[type=password]:focus, +input[type=email]:focus, input[type=url]:focus, input[type=number]:focus, +input[type=tel]:focus, textarea:focus, select:focus, .vTextField:focus { border-color: var(--body-quiet-color); } @@ -586,7 +600,7 @@ input[type=button][disabled].default { font-weight: 400; font-size: 0.8125rem; text-align: left; - background: var(--primary); + background: var(--header-bg); color: var(--header-link-color); } @@ -722,6 +736,11 @@ div.breadcrumbs a:focus, div.breadcrumbs a:hover { background: url(../img/icon-viewlink.svg) 0 1px no-repeat; } +.hidelink { + padding-left: 16px; + background: url(../img/icon-hidelink.svg) 0 1px no-repeat; +} + .addlink { padding-left: 16px; background: url(../img/icon-addlink.svg) 0 1px no-repeat; @@ -831,10 +850,6 @@ a.deletelink:focus, a.deletelink:hover { height: 100%; } -#container > div { - flex-shrink: 0; -} - #container > .main { display: flex; flex: 1 0 auto; @@ -879,9 +894,10 @@ a.deletelink:focus, a.deletelink:hover { margin-right: -300px; } -#footer { - clear: both; - padding: 10px; +@media (forced-colors: active) { + #content-related { + border: 1px solid; + } } /* COLUMN TYPES */ @@ -919,7 +935,6 @@ a.deletelink:focus, a.deletelink:hover { padding: 10px 40px; background: var(--header-bg); color: var(--header-color); - overflow: hidden; } #header a:link, #header a:visited, #logout-form button { @@ -930,11 +945,17 @@ a.deletelink:focus, a.deletelink:hover { text-decoration: underline; } +@media (forced-colors: active) { + #header { + border-bottom: 1px solid; + } +} + #branding { display: flex; } -#branding h1 { +#site-name { padding: 0; margin: 0; margin-inline-end: 20px; @@ -943,7 +964,7 @@ a.deletelink:focus, a.deletelink:hover { color: var(--header-branding-color); } -#branding h1 a:link, #branding h1 a:visited { +#site-name a:link, #site-name a:visited { color: var(--accent); } @@ -1143,3 +1164,16 @@ a.deletelink:focus, a.deletelink:hover { .base-svgs { display: none; } + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0,0,0,0); + white-space: nowrap; + border: 0; + color: var(--body-fg); + background-color: var(--body-bg); +} diff --git a/staticfiles/admin/css/changelists.css b/staticfiles/admin/css/changelists.css index a7545131..005b7768 100644 --- a/staticfiles/admin/css/changelists.css +++ b/staticfiles/admin/css/changelists.css @@ -139,6 +139,12 @@ margin: 0 0 0 30px; } +@media (forced-colors: active) { + #changelist-filter { + border: 1px solid; + } +} + #changelist-filter h2 { font-size: 0.875rem; text-transform: uppercase; @@ -215,9 +221,9 @@ color: var(--link-hover-color); } -#changelist-filter #changelist-filter-clear a { +#changelist-filter #changelist-filter-extra-actions { font-size: 0.8125rem; - padding-bottom: 10px; + margin-bottom: 10px; border-bottom: 1px solid var(--hairline-color); } @@ -265,6 +271,15 @@ background-color: var(--selected-row); } +@media (forced-colors: active) { + #changelist tbody tr.selected { + background-color: SelectedItem; + } + #changelist tbody tr:has(.action-select:checked) { + background-color: SelectedItem; + } +} + #changelist .actions { padding: 10px; background: var(--body-bg); diff --git a/staticfiles/admin/css/dark_mode.css b/staticfiles/admin/css/dark_mode.css index 6d08233a..7e12a815 100644 --- a/staticfiles/admin/css/dark_mode.css +++ b/staticfiles/admin/css/dark_mode.css @@ -5,7 +5,8 @@ --body-fg: #eeeeee; --body-bg: #121212; - --body-quiet-color: #e0e0e0; + --body-quiet-color: #d0d0d0; + --body-medium-color: #e0e0e0; --body-loud-color: #ffffff; --breadcrumbs-link-fg: #e0e0e0; @@ -29,6 +30,8 @@ --close-button-bg: #333333; --close-button-hover-bg: #666666; + + color-scheme: dark; } } @@ -39,7 +42,8 @@ html[data-theme="dark"] { --body-fg: #eeeeee; --body-bg: #121212; - --body-quiet-color: #e0e0e0; + --body-quiet-color: #d0d0d0; + --body-medium-color: #e0e0e0; --body-loud-color: #ffffff; --breadcrumbs-link-fg: #e0e0e0; @@ -63,6 +67,8 @@ html[data-theme="dark"] { --close-button-bg: #333333; --close-button-hover-bg: #666666; + + color-scheme: dark; } /* THEME SWITCH */ @@ -122,16 +128,3 @@ html[data-theme="dark"] .theme-toggle svg.theme-icon-when-dark { html[data-theme="light"] .theme-toggle svg.theme-icon-when-light { display: block; } - -.visually-hidden { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - overflow: hidden; - clip: rect(0,0,0,0); - white-space: nowrap; - border: 0; - color: var(--body-fg); - background-color: var(--body-bg); -} diff --git a/staticfiles/admin/css/forms.css b/staticfiles/admin/css/forms.css index 9a8dad08..4f49b613 100644 --- a/staticfiles/admin/css/forms.css +++ b/staticfiles/admin/css/forms.css @@ -44,7 +44,6 @@ label { .required label, label.required { font-weight: bold; - color: var(--body-fg); } /* RADIO BUTTONS */ @@ -76,6 +75,20 @@ form ul.inline li { padding-right: 7px; } +/* FIELDSETS */ + +fieldset .fieldset-heading, +fieldset .inline-heading, +:not(.inline-related) .collapse summary { + border: 1px solid var(--header-bg); + margin: 0; + padding: 8px; + font-weight: 400; + font-size: 0.8125rem; + background: var(--header-bg); + color: var(--header-link-color); +} + /* ALIGNED FIELDSETS */ .aligned label { @@ -84,14 +97,12 @@ form ul.inline li { min-width: 160px; width: 160px; word-wrap: break-word; - line-height: 1; } .aligned label:not(.vCheckboxLabel):after { content: ''; display: inline-block; vertical-align: middle; - height: 1.625rem; } .aligned label + p, .aligned .checkbox-row + div.help, .aligned label + div.readonly { @@ -158,6 +169,10 @@ form .aligned select + div.help { padding-left: 10px; } +form .aligned select option:checked { + background-color: var(--selected-row); +} + form .aligned ul li { list-style: none; } @@ -168,11 +183,7 @@ form .aligned table p { } .aligned .vCheckboxLabel { - float: none; - width: auto; - display: inline-block; - vertical-align: -3px; - padding: 0 0 5px 5px; + padding: 1px 0 0 5px; } .aligned .vCheckboxLabel + p.help, @@ -194,14 +205,8 @@ fieldset .fieldBox { width: 200px; } -form .wide p, -form .wide ul.errorlist, -form .wide input + p.help, -form .wide input + div.help { - margin-left: 200px; -} - form .wide p.help, +form .wide ul.errorlist, form .wide div.help { padding-left: 50px; } @@ -215,35 +220,16 @@ form div.help ul { width: 450px; } -/* COLLAPSED FIELDSETS */ +/* COLLAPSIBLE FIELDSETS */ -fieldset.collapsed * { - display: none; -} - -fieldset.collapsed h2, fieldset.collapsed { - display: block; -} - -fieldset.collapsed { - border: 1px solid var(--hairline-color); - border-radius: 4px; - overflow: hidden; -} - -fieldset.collapsed h2 { - background: var(--darkened-bg); - color: var(--body-quiet-color); -} - -fieldset .collapse-toggle { - color: var(--header-link-color); -} - -fieldset.collapsed .collapse-toggle { +.collapse summary .fieldset-heading, +.collapse summary .inline-heading { background: transparent; + border: none; + color: currentColor; display: inline; - color: var(--link-fg); + margin: 0; + padding: 0; } /* MONOSPACE TEXTAREAS */ @@ -395,14 +381,16 @@ body.popup .submit-row { position: relative; } -.inline-related h3 { +.inline-related h4, +.inline-related:not(.tabular) .collapse summary { margin: 0; - color: var(--body-quiet-color); + color: var(--body-medium-color); padding: 5px; font-size: 0.8125rem; background: var(--darkened-bg); - border-top: 1px solid var(--hairline-color); - border-bottom: 1px solid var(--hairline-color); + border: 1px solid var(--hairline-color); + border-left-color: var(--darkened-bg); + border-right-color: var(--darkened-bg); } .inline-related h3 span.delete { @@ -421,16 +409,6 @@ body.popup .submit-row { width: 100%; } -.inline-related fieldset.module h3 { - margin: 0; - padding: 2px 5px 3px 5px; - font-size: 0.6875rem; - text-align: left; - font-weight: bold; - background: #bcd; - color: var(--body-bg); -} - .inline-group .tabular fieldset.module { border: none; } diff --git a/staticfiles/admin/css/login.css b/staticfiles/admin/css/login.css index 389772f5..805a34b5 100644 --- a/staticfiles/admin/css/login.css +++ b/staticfiles/admin/css/login.css @@ -21,7 +21,7 @@ } .login #content { - padding: 20px 20px 0; + padding: 20px; } .login #container { diff --git a/staticfiles/admin/css/nav_sidebar.css b/staticfiles/admin/css/nav_sidebar.css index f76e6ce4..7eb0de97 100644 --- a/staticfiles/admin/css/nav_sidebar.css +++ b/staticfiles/admin/css/nav_sidebar.css @@ -102,6 +102,12 @@ background: var(--selected-row); } +@media (forced-colors: active) { + #nav-sidebar .current-model { + background-color: SelectedItem; + } +} + .main > #nav-sidebar + .content { max-width: calc(100% - 23px); } diff --git a/staticfiles/admin/css/responsive.css b/staticfiles/admin/css/responsive.css index 1d0a188f..932e824c 100644 --- a/staticfiles/admin/css/responsive.css +++ b/staticfiles/admin/css/responsive.css @@ -43,7 +43,7 @@ input[type="submit"], button { justify-content: flex-start; } - #branding h1 { + #site-name { margin: 0 0 8px; line-height: 1.2; } @@ -171,9 +171,14 @@ input[type="submit"], button { /* Forms */ label { - font-size: 0.875rem; + font-size: 1rem; } + /* + Minifiers remove the default (text) "type" attribute from "input" HTML + tags. Add input:not([type]) to make the CSS stylesheet work the same. + */ + .form-row input:not([type]), .form-row input[type=text], .form-row input[type=password], .form-row input[type=email], @@ -187,7 +192,7 @@ input[type="submit"], button { margin: 0; padding: 6px 8px; min-height: 2.25rem; - font-size: 0.875rem; + font-size: 1rem; } .form-row select { @@ -237,22 +242,6 @@ input[type="submit"], button { padding: 7px; } - /* Related widget */ - - .related-widget-wrapper { - float: none; - } - - .related-widget-wrapper-link + .selector { - max-width: calc(100% - 30px); - margin-right: 15px; - } - - select + .related-widget-wrapper-link, - .related-widget-wrapper-link + .related-widget-wrapper-link { - margin-left: 10px; - } - /* Selector */ .selector { @@ -270,7 +259,7 @@ input[type="submit"], button { } .selector .selector-filter input { - width: auto; + width: 100%; min-height: 0; flex: 1 1; } @@ -292,7 +281,6 @@ input[type="submit"], button { width: 26px; height: 52px; padding: 2px 0; - margin: auto 15px; border-radius: 20px; transform: translateY(-10px); } @@ -336,7 +324,6 @@ input[type="submit"], button { width: 52px; height: 26px; padding: 0 2px; - margin: 15px auto; transform: none; } @@ -432,7 +419,7 @@ input[type="submit"], button { padding: 15px 20px; } - .login #branding h1 { + .login #site-name { margin: 0; } @@ -464,14 +451,10 @@ input[type="submit"], button { @media (max-width: 767px) { /* Layout */ - #header, #content, #footer { + #header, #content { padding: 15px; } - #footer:empty { - padding: 0; - } - div.breadcrumbs { padding: 10px 15px; } @@ -582,10 +565,6 @@ input[type="submit"], button { padding-top: 15px; } - fieldset.collapsed .form-row { - display: none; - } - .aligned label { width: 100%; min-width: auto; @@ -684,23 +663,14 @@ input[type="submit"], button { align-self: center; } - select + .related-widget-wrapper-link, - .related-widget-wrapper-link + .related-widget-wrapper-link { - margin-left: 15px; - } - /* Selector */ .selector { flex-direction: column; - } - - .selector > * { - float: none; + gap: 10px 0; } .selector-available, .selector-chosen { - margin-bottom: 0; flex: 1 1 auto; } @@ -710,11 +680,9 @@ input[type="submit"], button { .selector ul.selector-chooser { display: block; - float: none; width: 52px; height: 26px; padding: 0 2px; - margin: 15px auto 20px; transform: none; } diff --git a/staticfiles/admin/css/responsive_rtl.css b/staticfiles/admin/css/responsive_rtl.css index 31dc8ff7..33b57848 100644 --- a/staticfiles/admin/css/responsive_rtl.css +++ b/staticfiles/admin/css/responsive_rtl.css @@ -35,11 +35,6 @@ background-position: calc(100% - 8px) 9px; } - [dir="rtl"] .related-widget-wrapper-link + .selector { - margin-right: 0; - margin-left: 15px; - } - [dir="rtl"] .selector .selector-filter label { margin-right: 0; margin-left: 8px; @@ -58,6 +53,22 @@ padding-left: 0; padding-right: 16px; } + + [dir="rtl"] .selector-add { + background-position: 0 -80px; + } + + [dir="rtl"] .selector-remove { + background-position: 0 -120px; + } + + [dir="rtl"] .active.selector-add:focus, .active.selector-add:hover { + background-position: 0 -100px; + } + + [dir="rtl"] .active.selector-remove:focus, .active.selector-remove:hover { + background-position: 0 -140px; + } } /* MOBILE */ @@ -81,4 +92,20 @@ [dir="rtl"] .aligned .vCheckboxLabel { padding: 1px 5px 0 0; } + + [dir="rtl"] .selector-remove { + background-position: 0 0; + } + + [dir="rtl"] .active.selector-remove:focus, .active.selector-remove:hover { + background-position: 0 -20px; + } + + [dir="rtl"] .selector-add { + background-position: 0 -40px; + } + + [dir="rtl"] .active.selector-add:focus, .active.selector-add:hover { + background-position: 0 -60px; + } } diff --git a/staticfiles/admin/css/rtl.css b/staticfiles/admin/css/rtl.css index c349a939..b8f60e0a 100644 --- a/staticfiles/admin/css/rtl.css +++ b/staticfiles/admin/css/rtl.css @@ -13,7 +13,7 @@ th { margin-right: 1.5em; } -.viewlink, .addlink, .changelink { +.viewlink, .addlink, .changelink, .hidelink { padding-left: 0; padding-right: 16px; background-position: 100% 1px; @@ -151,6 +151,7 @@ form ul.inline li { form .aligned p.help, form .aligned div.help { + margin-left: 0; margin-right: 160px; padding-right: 10px; } @@ -164,19 +165,13 @@ form .aligned p.time div.help.timezonewarning { padding-right: 0; } -form .wide p.help, form .wide div.help { +form .wide p.help, +form .wide ul.errorlist, +form .wide div.help { padding-left: 0; padding-right: 50px; } -form .wide p, -form .wide ul.errorlist, -form .wide input + p.help, -form .wide input + div.help { - margin-right: 200px; - margin-left: 0px; -} - .submit-row { text-align: right; } @@ -202,12 +197,7 @@ fieldset .fieldBox { top: 0; left: auto; right: 10px; - background: url(../img/calendar-icons.svg) 0 -30px no-repeat; -} - -.calendarbox .calendarnav-previous:focus, -.calendarbox .calendarnav-previous:hover { - background-position: 0 -45px; + background: url(../img/calendar-icons.svg) 0 -15px no-repeat; } .calendarnav-next { @@ -217,11 +207,6 @@ fieldset .fieldBox { background: url(../img/calendar-icons.svg) 0 0 no-repeat; } -.calendarbox .calendarnav-next:focus, -.calendarbox .calendarnav-next:hover { - background-position: 0 -15px; -} - .calendar caption, .calendarbox h2 { text-align: center; } @@ -296,3 +281,11 @@ form .form-row p.datetime { margin-left: inherit; margin-right: 2px; } + +.inline-group .tabular td.original p { + right: 0; +} + +.selector .selector-chooser { + margin: 0; +} diff --git a/staticfiles/admin/css/unusable_password_field.css b/staticfiles/admin/css/unusable_password_field.css new file mode 100644 index 00000000..d46eb038 --- /dev/null +++ b/staticfiles/admin/css/unusable_password_field.css @@ -0,0 +1,19 @@ +/* Hide warnings fields if usable password is selected */ +form:has(#id_usable_password input[value="true"]:checked) .messagelist { + display: none; +} + +/* Hide password fields if unusable password is selected */ +form:has(#id_usable_password input[value="false"]:checked) .field-password1, +form:has(#id_usable_password input[value="false"]:checked) .field-password2 { + display: none; +} + +/* Select appropriate submit button */ +form:has(#id_usable_password input[value="true"]:checked) input[type="submit"].unset-password { + display: none; +} + +form:has(#id_usable_password input[value="false"]:checked) input[type="submit"].set-password { + display: none; +} diff --git a/staticfiles/admin/css/widgets.css b/staticfiles/admin/css/widgets.css index 1104e8b1..cc64811a 100644 --- a/staticfiles/admin/css/widgets.css +++ b/staticfiles/admin/css/widgets.css @@ -1,23 +1,23 @@ /* SELECTOR (FILTER INTERFACE) */ .selector { - width: 800px; - float: left; display: flex; + flex-grow: 1; + gap: 0 10px; } .selector select { - width: 380px; height: 17.2em; flex: 1 0 auto; + overflow: scroll; + width: 100%; } .selector-available, .selector-chosen { - width: 380px; text-align: center; - margin-bottom: 5px; display: flex; flex-direction: column; + flex: 1 1; } .selector-available h2, .selector-chosen h2 { @@ -41,7 +41,7 @@ } .selector-chosen h2 { - background: var(--primary); + background: var(--secondary); color: var(--header-link-color); } @@ -58,6 +58,7 @@ font-size: 0.625rem; margin: 0; text-align: left; + display: flex; } .selector .selector-filter label, @@ -72,9 +73,12 @@ min-width: auto; } +.selector-filter input { + flex-grow: 1; +} + .selector .selector-available input, .selector .selector-chosen input { - width: 320px; margin-left: 8px; } @@ -83,7 +87,7 @@ width: 22px; background-color: var(--selected-bg); border-radius: 10px; - margin: 0 5px; + margin: 0; padding: 0; transform: translateY(-17px); } @@ -147,7 +151,7 @@ a.selector-chooseall, a.selector-clearall { display: inline-block; height: 16px; text-align: left; - margin: 1px auto 3px; + margin: 0 auto; overflow: hidden; font-weight: bold; line-height: 16px; @@ -447,7 +451,7 @@ span.clearable-file-input label { } .calendar td.selected a { - background: var(--primary); + background: var(--secondary); color: var(--button-fg); } @@ -515,36 +519,26 @@ span.clearable-file-input label { background: url(../img/calendar-icons.svg) 0 0 no-repeat; } -.calendarbox .calendarnav-previous:focus, -.calendarbox .calendarnav-previous:hover { - background-position: 0 -15px; -} - .calendarnav-next { right: 10px; - background: url(../img/calendar-icons.svg) 0 -30px no-repeat; -} - -.calendarbox .calendarnav-next:focus, -.calendarbox .calendarnav-next:hover { - background-position: 0 -45px; + background: url(../img/calendar-icons.svg) 0 -15px no-repeat; } .calendar-cancel { margin: 0; padding: 4px 0; font-size: 0.75rem; - background: #eee; + background: var(--close-button-bg); border-top: 1px solid var(--border-color); - color: var(--body-fg); + color: var(--button-fg); } .calendar-cancel:focus, .calendar-cancel:hover { - background: #ddd; + background: var(--close-button-hover-bg); } .calendar-cancel a { - color: black; + color: var(--button-fg); display: block; } @@ -575,26 +569,21 @@ ul.timelist, .timelist li { /* RELATED WIDGET WRAPPER */ .related-widget-wrapper { - float: left; /* display properly in form rows with multiple fields */ - overflow: hidden; /* clear floated contents */ + display: flex; + gap: 0 10px; + flex-grow: 1; + flex-wrap: wrap; + margin-bottom: 5px; } .related-widget-wrapper-link { - opacity: 0.3; + opacity: .6; + filter: grayscale(1); } .related-widget-wrapper-link:link { - opacity: .8; -} - -.related-widget-wrapper-link:link:focus, -.related-widget-wrapper-link:link:hover { opacity: 1; -} - -select + .related-widget-wrapper-link, -.related-widget-wrapper-link + .related-widget-wrapper-link { - margin-left: 7px; + filter: grayscale(0); } /* GIS MAPS */ diff --git a/staticfiles/admin/img/README.txt b/staticfiles/admin/img/README.txt index 4eb2e492..bf81f35b 100644 --- a/staticfiles/admin/img/README.txt +++ b/staticfiles/admin/img/README.txt @@ -1,4 +1,4 @@ -All icons are taken from Font Awesome (http://fontawesome.io/) project. +All icons are taken from Font Awesome (https://fontawesome.com/) project. The Font Awesome font is licensed under the SIL OFL 1.1: - https://scripts.sil.org/OFL diff --git a/staticfiles/admin/img/calendar-icons.svg b/staticfiles/admin/img/calendar-icons.svg index dbf21c39..04c02741 100644 --- a/staticfiles/admin/img/calendar-icons.svg +++ b/staticfiles/admin/img/calendar-icons.svg @@ -1,14 +1,63 @@ - - - - + + + + + + - - + + - - - - + + diff --git a/staticfiles/admin/img/icon-addlink.svg b/staticfiles/admin/img/icon-addlink.svg index e004fb16..8d5c6a3a 100644 --- a/staticfiles/admin/img/icon-addlink.svg +++ b/staticfiles/admin/img/icon-addlink.svg @@ -1,3 +1,3 @@ - + diff --git a/staticfiles/admin/img/icon-changelink.svg b/staticfiles/admin/img/icon-changelink.svg index bbb137aa..592b093b 100644 --- a/staticfiles/admin/img/icon-changelink.svg +++ b/staticfiles/admin/img/icon-changelink.svg @@ -1,3 +1,3 @@ - + diff --git a/staticfiles/admin/img/icon-hidelink.svg b/staticfiles/admin/img/icon-hidelink.svg new file mode 100644 index 00000000..2a8b404b --- /dev/null +++ b/staticfiles/admin/img/icon-hidelink.svg @@ -0,0 +1,3 @@ + + + diff --git a/staticfiles/admin/js/SelectFilter2.js b/staticfiles/admin/js/SelectFilter2.js index 9a4e0a3a..69574124 100644 --- a/staticfiles/admin/js/SelectFilter2.js +++ b/staticfiles/admin/js/SelectFilter2.js @@ -1,4 +1,4 @@ -/*global SelectBox, gettext, interpolate, quickElement, SelectFilter*/ +/*global SelectBox, gettext, ngettext, interpolate, quickElement, SelectFilter*/ /* SelectFilter2 - Turns a multiple-select box into a filter interface. @@ -30,6 +30,9 @@ Requires core.js and SelectBox.js. //
or
const selector_div = quickElement('div', from_box.parentNode); + // Make sure the selector div is at the beginning so that the + // add link would be displayed to the right of the widget. + from_box.parentNode.prepend(selector_div); selector_div.className = is_stacked ? 'selector stacked' : 'selector'; //
diff --git a/staticfiles/admin/js/actions.js b/staticfiles/admin/js/actions.js index 20a5c143..04b25e96 100644 --- a/staticfiles/admin/js/actions.js +++ b/staticfiles/admin/js/actions.js @@ -1,4 +1,4 @@ -/*global gettext, interpolate, ngettext*/ +/*global gettext, interpolate, ngettext, Actions*/ 'use strict'; { function show(selector) { @@ -179,6 +179,9 @@ } }); } + // Sync counter when navigating to the page, such as through the back + // button. + window.addEventListener('pageshow', (event) => updateCounter(actionCheckboxes, options)); }; // Call function fn when the DOM is loaded and ready. If it is already diff --git a/staticfiles/admin/js/admin/RelatedObjectLookups.js b/staticfiles/admin/js/admin/RelatedObjectLookups.js index afb6b66c..bc3accea 100644 --- a/staticfiles/admin/js/admin/RelatedObjectLookups.js +++ b/staticfiles/admin/js/admin/RelatedObjectLookups.js @@ -79,9 +79,11 @@ siblings.each(function() { const elm = $(this); elm.attr('href', elm.attr('data-href-template').replace('__fk__', value)); + elm.removeAttr('aria-disabled'); }); } else { siblings.removeAttr('href'); + siblings.attr('aria-disabled', true); } } @@ -94,8 +96,8 @@ // Extract the model from the popup url '...//add/' or // '...///change/' depending the action (add or change). const modelName = path.split('/')[path.split('/').length - (objId ? 4 : 3)]; - // Exclude autocomplete selects. - const selectsRelated = document.querySelectorAll(`[data-model-ref="${modelName}"] select:not(.admin-autocomplete)`); + // Select elements with a specific model reference and context of "available-source". + const selectsRelated = document.querySelectorAll(`[data-model-ref="${modelName}"] [data-context="available-source"]`); selectsRelated.forEach(function(select) { if (currentSelect === select) { diff --git a/staticfiles/admin/js/calendar.js b/staticfiles/admin/js/calendar.js index a62d10a7..776310f7 100644 --- a/staticfiles/admin/js/calendar.js +++ b/staticfiles/admin/js/calendar.js @@ -36,6 +36,24 @@ depends on core.js for utility functions like removeChildren or quickElement pgettext('abbrev. month December', 'Dec') ], daysOfWeek: [ + gettext('Sunday'), + gettext('Monday'), + gettext('Tuesday'), + gettext('Wednesday'), + gettext('Thursday'), + gettext('Friday'), + gettext('Saturday') + ], + daysOfWeekAbbrev: [ + pgettext('abbrev. day Sunday', 'Sun'), + pgettext('abbrev. day Monday', 'Mon'), + pgettext('abbrev. day Tuesday', 'Tue'), + pgettext('abbrev. day Wednesday', 'Wed'), + pgettext('abbrev. day Thursday', 'Thur'), + pgettext('abbrev. day Friday', 'Fri'), + pgettext('abbrev. day Saturday', 'Sat') + ], + daysOfWeekInitial: [ pgettext('one letter Sunday', 'S'), pgettext('one letter Monday', 'M'), pgettext('one letter Tuesday', 'T'), @@ -98,7 +116,7 @@ depends on core.js for utility functions like removeChildren or quickElement // Draw days-of-week header let tableRow = quickElement('tr', tableBody); for (let i = 0; i < 7; i++) { - quickElement('th', tableRow, CalendarNamespace.daysOfWeek[(i + CalendarNamespace.firstDayOfWeek) % 7]); + quickElement('th', tableRow, CalendarNamespace.daysOfWeekInitial[(i + CalendarNamespace.firstDayOfWeek) % 7]); } const startingPos = new Date(year, month - 1, 1 - CalendarNamespace.firstDayOfWeek).getDay(); diff --git a/staticfiles/admin/js/core.js b/staticfiles/admin/js/core.js index 0344a13f..10504d4a 100644 --- a/staticfiles/admin/js/core.js +++ b/staticfiles/admin/js/core.js @@ -85,6 +85,18 @@ function findPosY(obj) { return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds(); }; + Date.prototype.getAbbrevDayName = function() { + return typeof window.CalendarNamespace === "undefined" + ? '0' + this.getDay() + : window.CalendarNamespace.daysOfWeekAbbrev[this.getDay()]; + }; + + Date.prototype.getFullDayName = function() { + return typeof window.CalendarNamespace === "undefined" + ? '0' + this.getDay() + : window.CalendarNamespace.daysOfWeek[this.getDay()]; + }; + Date.prototype.getAbbrevMonthName = function() { return typeof window.CalendarNamespace === "undefined" ? this.getTwoDigitMonth() @@ -99,6 +111,8 @@ function findPosY(obj) { Date.prototype.strftime = function(format) { const fields = { + a: this.getAbbrevDayName(), + A: this.getFullDayName(), b: this.getAbbrevMonthName(), B: this.getFullMonthName(), c: this.toString(), diff --git a/staticfiles/admin/js/popup_response.js b/staticfiles/admin/js/popup_response.js index 2b1d3dd3..fecf0f47 100644 --- a/staticfiles/admin/js/popup_response.js +++ b/staticfiles/admin/js/popup_response.js @@ -1,4 +1,3 @@ -/*global opener */ 'use strict'; { const initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse); diff --git a/staticfiles/admin/js/theme.js b/staticfiles/admin/js/theme.js index 794cd15f..e79d375c 100644 --- a/staticfiles/admin/js/theme.js +++ b/staticfiles/admin/js/theme.js @@ -1,56 +1,51 @@ 'use strict'; { - window.addEventListener('load', function(e) { - - function setTheme(mode) { - if (mode !== "light" && mode !== "dark" && mode !== "auto") { - console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`); - mode = "auto"; - } - document.documentElement.dataset.theme = mode; - localStorage.setItem("theme", mode); + function setTheme(mode) { + if (mode !== "light" && mode !== "dark" && mode !== "auto") { + console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`); + mode = "auto"; } + document.documentElement.dataset.theme = mode; + localStorage.setItem("theme", mode); + } - function cycleTheme() { - const currentTheme = localStorage.getItem("theme") || "auto"; - const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + function cycleTheme() { + const currentTheme = localStorage.getItem("theme") || "auto"; + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; - if (prefersDark) { - // Auto (dark) -> Light -> Dark - if (currentTheme === "auto") { - setTheme("light"); - } else if (currentTheme === "light") { - setTheme("dark"); - } else { - setTheme("auto"); - } + if (prefersDark) { + // Auto (dark) -> Light -> Dark + if (currentTheme === "auto") { + setTheme("light"); + } else if (currentTheme === "light") { + setTheme("dark"); } else { - // Auto (light) -> Dark -> Light - if (currentTheme === "auto") { - setTheme("dark"); - } else if (currentTheme === "dark") { - setTheme("light"); - } else { - setTheme("auto"); - } + setTheme("auto"); + } + } else { + // Auto (light) -> Dark -> Light + if (currentTheme === "auto") { + setTheme("dark"); + } else if (currentTheme === "dark") { + setTheme("light"); + } else { + setTheme("auto"); } } + } - function initTheme() { - // set theme defined in localStorage if there is one, or fallback to auto mode - const currentTheme = localStorage.getItem("theme"); - currentTheme ? setTheme(currentTheme) : setTheme("auto"); - } + function initTheme() { + // set theme defined in localStorage if there is one, or fallback to auto mode + const currentTheme = localStorage.getItem("theme"); + currentTheme ? setTheme(currentTheme) : setTheme("auto"); + } - function setupTheme() { - // Attach event handlers for toggling themes - const buttons = document.getElementsByClassName("theme-toggle"); - Array.from(buttons).forEach((btn) => { - btn.addEventListener("click", cycleTheme); - }); - initTheme(); - } - - setupTheme(); + window.addEventListener('load', function(_) { + const buttons = document.getElementsByClassName("theme-toggle"); + Array.from(buttons).forEach((btn) => { + btn.addEventListener("click", cycleTheme); + }); }); + + initTheme(); } diff --git a/staticfiles/admin/js/unusable_password_field.js b/staticfiles/admin/js/unusable_password_field.js new file mode 100644 index 00000000..ec26238c --- /dev/null +++ b/staticfiles/admin/js/unusable_password_field.js @@ -0,0 +1,29 @@ +"use strict"; +// Fallback JS for browsers which do not support :has selector used in +// admin/css/unusable_password_fields.css +// Remove file once all supported browsers support :has selector +try { + // If browser does not support :has selector this will raise an error + document.querySelector("form:has(input)"); +} catch (error) { + console.log("Defaulting to javascript for usable password form management: " + error); + // JS replacement for unsupported :has selector + document.querySelectorAll('input[name="usable_password"]').forEach(option => { + option.addEventListener('change', function() { + const usablePassword = (this.value === "true" ? this.checked : !this.checked); + const submit1 = document.querySelector('input[type="submit"].set-password'); + const submit2 = document.querySelector('input[type="submit"].unset-password'); + const messages = document.querySelector('#id_unusable_warning'); + document.getElementById('id_password1').closest('.form-row').hidden = !usablePassword; + document.getElementById('id_password2').closest('.form-row').hidden = !usablePassword; + if (messages) { + messages.hidden = usablePassword; + } + if (submit1 && submit2) { + submit1.hidden = !usablePassword; + submit2.hidden = usablePassword; + } + }); + option.dispatchEvent(new Event('change')); + }); +} diff --git a/staticfiles/admin/js/vendor/jquery/jquery.js b/staticfiles/admin/js/vendor/jquery/jquery.js index 7f35c11b..1a86433c 100644 --- a/staticfiles/admin/js/vendor/jquery/jquery.js +++ b/staticfiles/admin/js/vendor/jquery/jquery.js @@ -1,15 +1,12 @@ /*! - * jQuery JavaScript Library v3.6.4 + * jQuery JavaScript Library v3.7.1 * https://jquery.com/ * - * Includes Sizzle.js - * https://sizzlejs.com/ - * * Copyright OpenJS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2023-03-08T15:28Z + * Date: 2023-08-28T13:37Z */ ( function( global, factory ) { @@ -150,8 +147,9 @@ function toType( obj ) { -var - version = "3.6.4", +var version = "3.7.1", + + rhtmlSuffix = /HTML$/i, // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -397,6 +395,38 @@ jQuery.extend( { return obj; }, + + // Retrieve the text value of an array of DOM nodes + text: function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += jQuery.text( node ); + } + } + if ( nodeType === 1 || nodeType === 11 ) { + return elem.textContent; + } + if ( nodeType === 9 ) { + return elem.documentElement.textContent; + } + if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; + }, + // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; @@ -419,6 +449,15 @@ jQuery.extend( { return arr == null ? -1 : indexOf.call( arr, elem, i ); }, + isXMLDoc: function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + // Assume HTML when documentElement doesn't yet exist, such as inside + // document fragments. + return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" ); + }, + // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit merge: function( first, second ) { @@ -520,43 +559,98 @@ function isArrayLike( obj ) { return type === "array" || length === 0 || typeof length === "number" && length > 0 && ( length - 1 ) in obj; } -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.10 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2023-02-14 - */ -( function( window ) { + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var pop = arr.pop; + + +var sort = arr.sort; + + +var splice = arr.splice; + + +var whitespace = "[\\x20\\t\\r\\n\\f]"; + + +var rtrimCSS = new RegExp( + "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", + "g" +); + + + + +// Note: an element does not contain itself +jQuery.contains = function( a, b ) { + var bup = b && b.parentNode; + + return a === bup || !!( bup && bup.nodeType === 1 && ( + + // Support: IE 9 - 11+ + // IE doesn't have `contains` on SVG. + a.contains ? + a.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); +}; + + + + +// CSS string/identifier serialization +// https://drafts.csswg.org/cssom/#common-serializing-idioms +var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + +function fcssescape( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; +} + +jQuery.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + + + + +var preferredDoc = document, + pushNative = push; + +( function() { + var i, - support, Expr, - getText, - isXML, - tokenize, - compile, - select, outermostContext, sortInput, hasDuplicate, + push = pushNative, // Local document vars - setDocument, document, - docElem, + documentElement, documentIsHTML, rbuggyQSA, - rbuggyMatches, matches, - contains, // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, + expando = jQuery.expando, dirruns = 0, done = 0, classCache = createCache(), @@ -570,47 +664,22 @@ var i, return 0; }, - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + + "loop|multiple|open|readonly|required|scoped", // Regular expressions - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", @@ -629,101 +698,88 @@ var i, // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), + rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + + whitespace + "*" ), rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( + "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + bool: new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + + needsContext: new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, - rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, - rnative = /^[^{]+\{\s*\[native \w/, - // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\([^\\r\\n\\f])", "g" ), funescape = function( escape, nonHex ) { var high = "0x" + escape.slice( 1 ) - 0x10000; - return nonHex ? + if ( nonHex ) { // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + return nonHex; } - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + return high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, - // Used for iframes - // See setDocument() + // Used for iframes; see `setDocument`. + // Support: IE 9 - 11+, Edge 12 - 18+ // Removing the function wrapper causes a "Permission Denied" - // error in IE + // error in IE/Edge. unloadHandler = function() { setDocument(); }, inDisabledFieldset = addCombinator( function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + return elem.disabled === true && nodeName( elem, "fieldset" ); }, { dir: "parentNode", next: "legend" } ); +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + // Optimize for push.apply( _, NodeList ) try { push.apply( @@ -731,32 +787,22 @@ try { preferredDoc.childNodes ); - // Support: Android<4.0 + // Support: Android <=4.0 // Detect silently failing push.apply // eslint-disable-next-line no-unused-expressions arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { + push = { + apply: function( target, els ) { pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; + }, + call: function( target ) { + pushNative.apply( target, slice.call( arguments, 1 ) ); } }; } -function Sizzle( selector, context, results, seed ) { +function find( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, @@ -790,11 +836,10 @@ function Sizzle( selector, context, results, seed ) { if ( nodeType === 9 ) { if ( ( elem = context.getElementById( m ) ) ) { - // Support: IE, Opera, Webkit - // TODO: identify versions + // Support: IE 9 only // getElementById can match elements by name instead of ID if ( elem.id === m ) { - results.push( elem ); + push.call( results, elem ); return results; } } else { @@ -804,14 +849,13 @@ function Sizzle( selector, context, results, seed ) { // Element context } else { - // Support: IE, Opera, Webkit - // TODO: identify versions + // Support: IE 9 only // getElementById can match elements by name instead of ID if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && + find.contains( context, elem ) && elem.id === m ) { - results.push( elem ); + push.call( results, elem ); return results; } } @@ -822,22 +866,15 @@ function Sizzle( selector, context, results, seed ) { return results; // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - + } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + if ( !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { newSelector = selector; newContext = context; @@ -858,11 +895,15 @@ function Sizzle( selector, context, results, seed ) { // We can use :scope instead of the ID hack if the browser // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when + // strict-comparing two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( newContext != context || !support.scope ) { // Capture the context ID, setting it first if necessary if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); + nid = jQuery.escapeSelector( nid ); } else { context.setAttribute( "id", ( nid = expando ) ); } @@ -895,7 +936,7 @@ function Sizzle( selector, context, results, seed ) { } // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); + return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); } /** @@ -909,7 +950,8 @@ function createCache() { function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + // Use (key + " ") to avoid collision with native prototype properties + // (see https://github.com/jquery/sizzle/issues/157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries @@ -921,7 +963,7 @@ function createCache() { } /** - * Mark a function for special use by Sizzle + * Mark a function for special use by jQuery selector module * @param {Function} fn The function to mark */ function markFunction( fn ) { @@ -952,56 +994,13 @@ function assert( fn ) { } } -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; + return nodeName( elem, "input" ) && elem.type === type; }; } @@ -1011,8 +1010,8 @@ function createInputPseudo( type ) { */ function createButtonPseudo( type ) { return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; + return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && + elem.type === type; }; } @@ -1048,14 +1047,13 @@ function createDisabledPseudo( disabled ) { } } - // Support: IE 6 - 11 + // Support: IE 6 - 11+ // Use the isDisabled shortcut property to check for disabled fieldset ancestors return elem.isDisabled === disabled || // Where there is no isDisabled, check manually - /* jshint -W018 */ elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; + inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; @@ -1095,7 +1093,7 @@ function createPositionalPseudo( fn ) { } /** - * Checks a node for validity as a Sizzle context + * Checks a node for validity as a jQuery selector context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ @@ -1103,31 +1101,13 @@ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem && elem.namespaceURI, - docElem = elem && ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - /** * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document + * @param {Element|Object} [node] An element or document object to use to set the document * @returns {Object} Returns the current document */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, +function setDocument( node ) { + var subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected @@ -1141,41 +1121,58 @@ setDocument = Sizzle.setDocument = function( node ) { // Update global variables document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); + documentElement = document.documentElement; + documentIsHTML = !jQuery.isXMLDoc( document ); + + // Support: iOS 7 only, IE 9 - 11+ + // Older browsers didn't support unprefixed `matches`. + matches = documentElement.matches || + documentElement.webkitMatchesSelector || + documentElement.msMatchesSelector; // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && + // Accessing iframe documents after unload throws "permission denied" errors + // (see trac-13936). + // Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`, + // all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well. + if ( documentElement.msMatchesSelector && + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + preferredDoc != document && ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } + // Support: IE 9 - 11+, Edge 12 - 18+ + subWindow.addEventListener( "unload", unloadHandler ); } - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; + // Support: IE <10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + documentElement.appendChild( el ).id = jQuery.expando; + return !document.getElementsByName || + !document.getElementsByName( jQuery.expando ).length; } ); - // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+ - // Make sure the the `:has()` argument is parsed unforgivingly. + // Support: IE 9 only + // Check to see if it's possible to do matchesSelector + // on a disconnected node. + support.disconnectedMatch = assert( function( el ) { + return matches.call( el, "*" ); + } ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // IE/Edge don't support the :scope pseudo-class. + support.scope = assert( function() { + return document.querySelectorAll( ":scope" ); + } ); + + // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only + // Make sure the `:has()` argument is parsed unforgivingly. // We include `*` in the test to detect buggy implementations that are // _selectively_ forgiving (specifically when the list includes at least // one valid selector). @@ -1192,54 +1189,22 @@ setDocument = Sizzle.setDocument = function( node ) { } } ); - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - } ); - // ID filter and find if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { + Expr.filter.ID = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute( "id" ) === attrId; }; }; - Expr.find[ "ID" ] = function( id, context ) { + Expr.find.ID = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { - Expr.filter[ "ID" ] = function( id ) { + Expr.filter.ID = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && @@ -1250,7 +1215,7 @@ setDocument = Sizzle.setDocument = function( node ) { // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { + Expr.find.ID = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); @@ -1280,40 +1245,18 @@ setDocument = Sizzle.setDocument = function( node ) { } // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); + Expr.find.TAG = function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; + // DocumentFragment nodes don't have gEBTN + } else { + return context.querySelectorAll( tag ); + } + }; // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + Expr.find.CLASS = function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } @@ -1324,139 +1267,75 @@ setDocument = Sizzle.setDocument = function( node ) { // QSA and matchesSelector support - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { + var input; - var input; + documentElement.appendChild( el ).innerHTML = + "" + + ""; - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; + // Support: iOS <=7 - 8 only + // Boolean attributes and "value" are not treated correctly in some XML documents + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } + // Support: iOS <=7 - 8 only + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } + // Support: iOS 8 only + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } + // Support: IE 9 - 11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + documentElement.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + } ); if ( !support.cssHas ) { @@ -1470,49 +1349,12 @@ setDocument = Sizzle.setDocument = function( node ) { } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - - // Support: IE <9 only - // IE doesn't have `contains` on `document` so we need to check for - // `documentElement` presence. - // We need to fall back to `a` when `documentElement` is missing - // as `ownerDocument` of elements within `