update
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.1.3 on 2024-12-13 19:04
|
# Generated by Django 5.2.7 on 2025-12-06 15:57
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
@@ -15,6 +15,51 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Notification',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('title', models.CharField(max_length=255, verbose_name='Title')),
|
||||||
|
('description', models.TextField(verbose_name='Description')),
|
||||||
|
('notification_type', models.CharField(choices=[('System', 'System'), ('Another', 'Another')], max_length=255, verbose_name='Type')),
|
||||||
|
('long', models.FloatField(verbose_name='Long')),
|
||||||
|
('lat', models.FloatField(verbose_name='Lat')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Notification',
|
||||||
|
'verbose_name_plural': 'Notifications',
|
||||||
|
'db_table': 'notification',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserLike',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'User Like',
|
||||||
|
'verbose_name_plural': 'User Likes',
|
||||||
|
'db_table': 'user_like',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserNotification',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('is_read', models.BooleanField(default=False, verbose_name='Read')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'User Notification',
|
||||||
|
'verbose_name_plural': 'User Notifications',
|
||||||
|
'db_table': 'user_notification',
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='User',
|
name='User',
|
||||||
fields=[
|
fields=[
|
||||||
@@ -33,7 +78,10 @@ class Migration(migrations.Migration):
|
|||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
('validated_at', models.DateTimeField(blank=True, null=True)),
|
('validated_at', models.DateTimeField(blank=True, null=True)),
|
||||||
|
('is_verify', models.BooleanField(default=False)),
|
||||||
|
('account_type', models.CharField(choices=[('personal', 'Personal'), ('business', 'Business')], default='personal', max_length=255)),
|
||||||
('role', models.CharField(choices=[('superuser', 'Superuser'), ('admin', 'Admin'), ('user', 'User')], default='user', max_length=255)),
|
('role', models.CharField(choices=[('superuser', 'Superuser'), ('admin', 'Admin'), ('user', 'User')], default='user', max_length=255)),
|
||||||
|
('avatar', models.ImageField(default='avatars/default.png', upload_to='avatars/', verbose_name='Avatar')),
|
||||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
],
|
],
|
||||||
@@ -43,6 +91,45 @@ class Migration(migrations.Migration):
|
|||||||
'abstract': False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Address',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='addresses', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Address',
|
||||||
|
'verbose_name_plural': 'Addresses',
|
||||||
|
'db_table': 'address',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Business',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='Business Name')),
|
||||||
|
('work_time', models.CharField(max_length=255, verbose_name='Work Time')),
|
||||||
|
('contact', models.CharField(max_length=255, verbose_name='Contact')),
|
||||||
|
('instagram', models.CharField(max_length=255, verbose_name='Instagram')),
|
||||||
|
('facebook', models.CharField(max_length=255, verbose_name='Facebook')),
|
||||||
|
('telegram', models.CharField(max_length=255, verbose_name='Telegram')),
|
||||||
|
('bio', models.TextField(verbose_name='Bio')),
|
||||||
|
('address', models.CharField(max_length=255, verbose_name='Address Name')),
|
||||||
|
('long', models.FloatField(verbose_name='Longitude')),
|
||||||
|
('lat', models.FloatField(verbose_name='Latitude')),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='business', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Business',
|
||||||
|
'verbose_name_plural': 'Business',
|
||||||
|
'db_table': 'business',
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ResetToken',
|
name='ResetToken',
|
||||||
fields=[
|
fields=[
|
||||||
@@ -57,4 +144,19 @@ class Migration(migrations.Migration):
|
|||||||
'verbose_name_plural': 'Reset Tokens',
|
'verbose_name_plural': 'Reset Tokens',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SearchHistory',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('value', models.CharField(max_length=255, verbose_name='Search History')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Search History',
|
||||||
|
'verbose_name_plural': 'Search History',
|
||||||
|
'db_table': 'search_history',
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
38
core/apps/accounts/migrations/0002_initial.py
Normal file
38
core/apps/accounts/migrations/0002_initial.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-06 15:57
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0001_initial'),
|
||||||
|
('api', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userlike',
|
||||||
|
name='ad',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes', to='api.admodel', verbose_name='Ad'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userlike',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes', to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='usernotification',
|
||||||
|
name='notification',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.notification', verbose_name='Notification'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='usernotification',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-22 07:29
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='account_type',
|
|
||||||
field=models.CharField(choices=[('personal', 'Personal'), ('business', 'Business')], default='personal', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='is_verify',
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-22 11:34
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0002_user_account_type_user_is_verify'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Address',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='addresses', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Address',
|
|
||||||
'verbose_name_plural': 'Addresses',
|
|
||||||
'db_table': 'address',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-24 06:45
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0003_address'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Business',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Business Name')),
|
|
||||||
('work_time', models.CharField(max_length=255, verbose_name='Work Time')),
|
|
||||||
('contact', models.CharField(max_length=255, verbose_name='Contact')),
|
|
||||||
('instagram', models.CharField(max_length=255, verbose_name='Instagram')),
|
|
||||||
('facebook', models.CharField(max_length=255, verbose_name='Facebook')),
|
|
||||||
('telegram', models.CharField(max_length=255, verbose_name='Telegram')),
|
|
||||||
('bio', models.TextField(verbose_name='Bio')),
|
|
||||||
('address_name', models.CharField(max_length=255, verbose_name='Address Name')),
|
|
||||||
('longitude', models.FloatField(verbose_name='Longitude')),
|
|
||||||
('latitude', models.FloatField(verbose_name='Latitude')),
|
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Business',
|
|
||||||
'verbose_name_plural': 'Business',
|
|
||||||
'db_table': 'business',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SearchHistory',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('value', models.CharField(max_length=255, verbose_name='Search History')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Search History',
|
|
||||||
'verbose_name_plural': 'Search History',
|
|
||||||
'db_table': 'search_history',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-26 10:04
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0004_business_searchhistory'),
|
|
||||||
('api', '0013_alter_feedback_comment'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserLike',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes', to='api.admodel', verbose_name='Ad')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'User Like',
|
|
||||||
'verbose_name_plural': 'User Likes',
|
|
||||||
'db_table': 'user_like',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-26 10:06
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0005_userlike'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='business',
|
|
||||||
old_name='address_name',
|
|
||||||
new_name='address',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='business',
|
|
||||||
old_name='latitude',
|
|
||||||
new_name='lat',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='business',
|
|
||||||
old_name='longitude',
|
|
||||||
new_name='long',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-26 12:24
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0006_rename_address_name_business_address_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Notification',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('title', models.CharField(max_length=255, verbose_name='Title')),
|
|
||||||
('description', models.TextField(verbose_name='Description')),
|
|
||||||
('notification_type', models.CharField(choices=[('System', 'System'), ('Another', 'Another')], max_length=255, verbose_name='Type')),
|
|
||||||
('long', models.FloatField(verbose_name='Long')),
|
|
||||||
('lat', models.FloatField(verbose_name='Lat')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Notification',
|
|
||||||
'verbose_name_plural': 'Notifications',
|
|
||||||
'db_table': 'notification',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserNotification',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('is_read', models.BooleanField(default=False, verbose_name='Read')),
|
|
||||||
('notification', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.notification', verbose_name='Notification')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'User Notification',
|
|
||||||
'verbose_name_plural': 'User Notifications',
|
|
||||||
'db_table': 'user_notification',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-28 10:40
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0007_notification_usernotification'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='avatar',
|
|
||||||
field=models.ImageField(default='resources/static/images/default.png', upload_to='avatars/', verbose_name='Avatar'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-28 10:43
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0008_user_avatar'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='user',
|
|
||||||
name='avatar',
|
|
||||||
field=models.ImageField(default='avatars/default.png', upload_to='avatars/', verbose_name='Avatar'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-28 11:02
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0009_alter_user_avatar'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='business',
|
|
||||||
name='user',
|
|
||||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='business', to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from .category import * # noqa
|
from .ad import *
|
||||||
from .ad import * # noqa
|
from .banner import *
|
||||||
from .ad_items import * # noqa
|
from .category import *
|
||||||
from .feedback import * # noqa
|
from .common import * # noqa
|
||||||
from .banner import * # noqa
|
from .feedback import *
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
from .ad import * # noqa
|
from .ad import *
|
||||||
|
from .image import *
|
||||||
|
from .option import *
|
||||||
|
from .variant import *
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin, TabularInline
|
||||||
|
|
||||||
from core.apps.api.models import AdModel, AdImage, AdVariant
|
from core.apps.api.models import AdModel, AdImageModel, AdVariantModel
|
||||||
|
|
||||||
|
|
||||||
class AdImageInline(admin.TabularInline):
|
class AdImageInline(TabularInline):
|
||||||
model = AdImage
|
model = AdImageModel
|
||||||
extra = 1
|
extra = 1
|
||||||
|
tab = True
|
||||||
|
|
||||||
|
|
||||||
class AdVariantInline(admin.TabularInline):
|
class AdVariantInline(TabularInline):
|
||||||
model = AdVariant
|
model = AdVariantModel
|
||||||
extra = 1
|
extra = 1
|
||||||
|
tab = True
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AdModel)
|
@admin.register(AdModel)
|
||||||
class AdModelAdmin(ModelAdmin):
|
class AdModelAdmin(ModelAdmin):
|
||||||
|
autocomplete_fields = ["tags"]
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
"__str__",
|
"__str__",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
from core.apps.api.models import AdImage
|
from core.apps.api.models import AdImageModel
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AdImage)
|
@admin.register(AdImageModel)
|
||||||
class AdImageAdmin(ModelAdmin):
|
class AdImageAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
from core.apps.api.models import AdOption
|
from core.apps.api.models import AdOptionModel
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AdOption)
|
@admin.register(AdOptionModel)
|
||||||
class AdOptionAdmin(ModelAdmin):
|
class AdOptionAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
from core.apps.api.models import AdVariant
|
from core.apps.api.models import AdVariantModel
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AdVariant)
|
@admin.register(AdVariantModel)
|
||||||
class AdVariantAdmin(ModelAdmin):
|
class AdVariantAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from .tags import * # noqa
|
|
||||||
from .ad_top_plan import * # noqa
|
|
||||||
from .ad_images import * # noqa
|
|
||||||
from .ad_variant import * # noqa
|
|
||||||
from .ad_options import * # noqa
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
from unfold.admin import ModelAdmin
|
|
||||||
|
|
||||||
from core.apps.api.models import AdTopPlan
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AdTopPlan)
|
|
||||||
class AdTopPlanAdmin(ModelAdmin):
|
|
||||||
list_display = (
|
|
||||||
"id",
|
|
||||||
"__str__",
|
|
||||||
)
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
from unfold.admin import ModelAdmin
|
|
||||||
|
|
||||||
from core.apps.api.models import Tags
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Tags)
|
|
||||||
class TagsAdmin(ModelAdmin):
|
|
||||||
list_display = (
|
|
||||||
"id",
|
|
||||||
"__str__",
|
|
||||||
)
|
|
||||||
@@ -1 +1 @@
|
|||||||
from .banner import * # noqa
|
from .banner import *
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
from core.apps.api.models import Banner
|
from core.apps.api.models import BannerModel
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Banner)
|
@admin.register(BannerModel)
|
||||||
class BannerAdmin(ModelAdmin):
|
class BannerAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from .category import * # noqa
|
from .category import *
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
from core.apps.api.models import Category
|
from core.apps.api.models import CategoryModel
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Category)
|
@admin.register(CategoryModel)
|
||||||
class CategoryAdmin(ModelAdmin):
|
class CategoryAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
|
|||||||
39
core/apps/api/admin/common.py
Normal file
39
core/apps/api/admin/common.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from core.apps.api.models import ColorModel, SizeModel
|
||||||
|
from core.apps.api.models import AdTopPlanModel
|
||||||
|
from core.apps.api.models import TagsModel
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ColorModel)
|
||||||
|
class ColorAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SizeModel)
|
||||||
|
class SizeAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AdTopPlanModel)
|
||||||
|
class AdTopPlanAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(TagsModel)
|
||||||
|
class TagsAdmin(ModelAdmin):
|
||||||
|
search_fields = ["name"]
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
@@ -1 +1 @@
|
|||||||
from .feedback import * # noqa
|
from .feedback import *
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
from core.apps.api.models import Feedback, FeedbackImages
|
from core.apps.api.models import FeedbackModel, FeedbackImageModel
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Feedback)
|
@admin.register(FeedbackModel)
|
||||||
class FeedbackAdmin(ModelAdmin):
|
class FeedbackAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
@@ -12,7 +12,7 @@ class FeedbackAdmin(ModelAdmin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(FeedbackImages)
|
@admin.register(FeedbackImageModel)
|
||||||
class FeedbackImagesAdmin(ModelAdmin):
|
class FeedbackImagesAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from .ad_type import * # noqa
|
from .ad_type import *
|
||||||
from .ad_variant_type import * # noqa
|
from .ad_variant_type import *
|
||||||
from .order_status import * # noqa
|
from .order_status import *
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
from .category import * # noqa
|
from .category import *
|
||||||
from .ad import * # noqa
|
from .ad import *
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
from django.db.models import (
|
from django.db.models import F, Case, When, FloatField, ExpressionWrapper, Subquery, OuterRef
|
||||||
F, Case, When, FloatField, ExpressionWrapper, Subquery, OuterRef
|
from core.apps.api.models import AdVariantModel, AdModel
|
||||||
)
|
|
||||||
from core.apps.api.models import AdVariant, AdModel
|
|
||||||
|
|
||||||
|
|
||||||
class AdFilter(filters.FilterSet):
|
class AdFilter(filters.FilterSet):
|
||||||
@@ -16,70 +14,28 @@ class AdFilter(filters.FilterSet):
|
|||||||
has_discount = filters.BooleanFilter(method="filter_has_discount")
|
has_discount = filters.BooleanFilter(method="filter_has_discount")
|
||||||
has_normal_user = filters.BooleanFilter(method="filter_has_normal_user")
|
has_normal_user = filters.BooleanFilter(method="filter_has_normal_user")
|
||||||
has_business_user = filters.BooleanFilter(method="filter_has_business_user")
|
has_business_user = filters.BooleanFilter(method="filter_has_business_user")
|
||||||
|
discount = filters.BooleanFilter(method="filter_discount")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdModel
|
model = AdModel
|
||||||
fields = ["min_price", "max_price"]
|
fields = ["min_price", "max_price"]
|
||||||
|
|
||||||
|
def filter_discount(self, queryset, name, value):
|
||||||
|
if value is True:
|
||||||
|
return queryset.filter(discount__gt=0)
|
||||||
|
return queryset.filter(discount=-1)
|
||||||
|
|
||||||
def filter_has_business_user(self, queryset, name, value):
|
def filter_has_business_user(self, queryset, name, value):
|
||||||
return queryset.filter(
|
return queryset.filter(user__account_type="business")
|
||||||
user__account_type="business"
|
|
||||||
)
|
|
||||||
|
|
||||||
def filter_has_normal_user(self, queryset, name, value):
|
def filter_has_normal_user(self, queryset, name, value):
|
||||||
return queryset.filter(
|
return queryset.filter(user__account_type="personal")
|
||||||
user__account_type="personal"
|
|
||||||
)
|
|
||||||
|
|
||||||
def filter_has_discount(self, queryset, name, value):
|
def filter_has_discount(self, queryset, name, value):
|
||||||
return queryset.filter(
|
return queryset.filter(variants__discount__gte=1).distinct()
|
||||||
variants__discount__gte=1
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
def filter_by_size(self, queryset, name, value):
|
def filter_by_size(self, queryset, name, value):
|
||||||
return queryset.filter(
|
return queryset.filter(variants__variant="Size", variants__value__iexact=value).distinct()
|
||||||
variants__variant="Size",
|
|
||||||
variants__value__iexact=value
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
def filter_by_color(self, queryset, name, value):
|
def filter_by_color(self, queryset, name, value):
|
||||||
return queryset.filter(
|
return queryset.filter(variants__variant="Color", variants__value__iexact=value).distinct()
|
||||||
variants__variant="Color",
|
|
||||||
variants__value__iexact=value
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
variant_real_price_expr = Case(
|
|
||||||
When(discount=-1, then=F("price")),
|
|
||||||
When(
|
|
||||||
discount__gte=0,
|
|
||||||
then=ExpressionWrapper(
|
|
||||||
F("price") - (F("price") * F("discount") / 100),
|
|
||||||
output_field=FloatField()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
output_field=FloatField()
|
|
||||||
)
|
|
||||||
|
|
||||||
cheapest_variant_qs = (
|
|
||||||
AdVariant.objects
|
|
||||||
.filter(ad=OuterRef("pk"))
|
|
||||||
.annotate(real_price=variant_real_price_expr)
|
|
||||||
.order_by("real_price")
|
|
||||||
.values("real_price")[:1]
|
|
||||||
)
|
|
||||||
|
|
||||||
ad_real_price = F("price")
|
|
||||||
|
|
||||||
queryset = queryset.annotate(
|
|
||||||
real_price=Case(
|
|
||||||
When(
|
|
||||||
variants__isnull=False,
|
|
||||||
then=Subquery(cheapest_variant_qs)
|
|
||||||
),
|
|
||||||
default=ad_real_price,
|
|
||||||
output_field=FloatField()
|
|
||||||
)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
return super().filter_queryset(queryset)
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
from core.apps.api.models import Category
|
from core.apps.api.models import CategoryModel
|
||||||
|
|
||||||
|
|
||||||
class CategoryFilter(filters.FilterSet):
|
class CategoryFilter(filters.FilterSet):
|
||||||
# name = filters.CharFilter(field_name="name", lookup_expr="icontains")
|
# name = filters.CharFilter(field_name="name", lookup_expr="icontains")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Category
|
model = CategoryModel
|
||||||
fields = [
|
fields = [
|
||||||
"show_home",
|
"show_home",
|
||||||
"id",
|
"id",
|
||||||
|
|||||||
0
core/apps/api/management/__init__.py
Normal file
0
core/apps/api/management/__init__.py
Normal file
0
core/apps/api/management/commands/__init__.py
Normal file
0
core/apps/api/management/commands/__init__.py
Normal file
400
core/apps/api/management/commands/create_fake_data.py
Normal file
400
core/apps/api/management/commands/create_fake_data.py
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
import random
|
||||||
|
from decimal import Decimal
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db import transaction
|
||||||
|
from core.apps.api.models import (
|
||||||
|
CategoryModel,
|
||||||
|
AdModel,
|
||||||
|
AdImageModel,
|
||||||
|
AdVariantModel,
|
||||||
|
AdOptionModel,
|
||||||
|
TagsModel,
|
||||||
|
ColorModel,
|
||||||
|
BannerModel,
|
||||||
|
FeedbackModel,
|
||||||
|
OrderModel,
|
||||||
|
OrderItemModel,
|
||||||
|
AdTopPlanModel,
|
||||||
|
)
|
||||||
|
from core.apps.api.choices import AdType, AdCategoryType, AdVariantType, OrderStatus
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Create fake data for testing API endpoints'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--users',
|
||||||
|
type=int,
|
||||||
|
default=5,
|
||||||
|
help='Number of users to create'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--ads',
|
||||||
|
type=int,
|
||||||
|
default=20,
|
||||||
|
help='Number of ads to create'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--clear',
|
||||||
|
action='store_true',
|
||||||
|
help='Clear existing data before creating new'
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
users_count = options['users']
|
||||||
|
ads_count = options['ads']
|
||||||
|
clear_data = options['clear']
|
||||||
|
|
||||||
|
if clear_data:
|
||||||
|
self.stdout.write(self.style.WARNING('Clearing existing data...'))
|
||||||
|
self.clear_data()
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS('Starting fake data generation...'))
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
# Create basic data
|
||||||
|
plan = self.create_plan()
|
||||||
|
colors = self.create_colors()
|
||||||
|
tags = self.create_tags()
|
||||||
|
categories = self.create_categories()
|
||||||
|
users = self.create_users(users_count)
|
||||||
|
|
||||||
|
# Create banners
|
||||||
|
banners = self.create_banners(categories[:3])
|
||||||
|
|
||||||
|
# Create ads with related data
|
||||||
|
ads = self.create_ads(ads_count, users, categories, plan, tags, colors)
|
||||||
|
|
||||||
|
# Create feedbacks
|
||||||
|
feedbacks = self.create_feedbacks(ads, users)
|
||||||
|
|
||||||
|
# Create orders
|
||||||
|
orders = self.create_orders(ads, users)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS('\n' + '='*50))
|
||||||
|
self.stdout.write(self.style.SUCCESS('Fake data created successfully!'))
|
||||||
|
self.stdout.write(self.style.SUCCESS('='*50))
|
||||||
|
self.stdout.write(f'Users: {len(users)}')
|
||||||
|
self.stdout.write(f'Categories: {len(categories)}')
|
||||||
|
self.stdout.write(f'Tags: {len(tags)}')
|
||||||
|
self.stdout.write(f'Colors: {len(colors)}')
|
||||||
|
self.stdout.write(f'Ads: {len(ads)}')
|
||||||
|
self.stdout.write(f'Feedbacks: {len(feedbacks)}')
|
||||||
|
self.stdout.write(f'Orders: {len(orders)}')
|
||||||
|
self.stdout.write(f'Banners: {len(banners)}')
|
||||||
|
|
||||||
|
def clear_data(self):
|
||||||
|
"""Clear all existing data"""
|
||||||
|
models = [
|
||||||
|
OrderItemModel,
|
||||||
|
OrderModel,
|
||||||
|
FeedbackModel,
|
||||||
|
AdImageModel,
|
||||||
|
AdVariantModel,
|
||||||
|
AdOptionModel,
|
||||||
|
AdModel,
|
||||||
|
BannerModel,
|
||||||
|
CategoryModel,
|
||||||
|
TagsModel,
|
||||||
|
ColorModel,
|
||||||
|
]
|
||||||
|
|
||||||
|
for model in models:
|
||||||
|
count = model.objects.all().delete()[0]
|
||||||
|
self.stdout.write(f'Deleted {count} {model.__name__} objects')
|
||||||
|
|
||||||
|
def create_plan(self):
|
||||||
|
"""Create or get default plan"""
|
||||||
|
plan, created = AdTopPlanModel.objects.get_or_create(
|
||||||
|
name="Free",
|
||||||
|
defaults={
|
||||||
|
'price': Decimal('0.00'),
|
||||||
|
'duration_days': 30,
|
||||||
|
'description': 'Free basic plan'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
self.stdout.write(self.style.SUCCESS('✓ Created default plan'))
|
||||||
|
return plan
|
||||||
|
|
||||||
|
def create_colors(self):
|
||||||
|
"""Create colors"""
|
||||||
|
colors_data = [
|
||||||
|
'Red', 'Blue', 'Green', 'Black', 'White',
|
||||||
|
'Yellow', 'Orange', 'Purple', 'Pink', 'Brown'
|
||||||
|
]
|
||||||
|
colors = []
|
||||||
|
for color_name in colors_data:
|
||||||
|
color, created = ColorModel.objects.get_or_create(name=color_name)
|
||||||
|
colors.append(color)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'✓ Created {len(colors)} colors'))
|
||||||
|
return colors
|
||||||
|
|
||||||
|
def create_tags(self):
|
||||||
|
"""Create tags"""
|
||||||
|
tags_data = [
|
||||||
|
'New', 'Hot', 'Sale', 'Popular', 'Featured',
|
||||||
|
'Limited', 'Exclusive', 'Premium', 'Budget', 'Trending'
|
||||||
|
]
|
||||||
|
tags = []
|
||||||
|
for tag_name in tags_data:
|
||||||
|
tag, created = TagsModel.objects.get_or_create(
|
||||||
|
name=tag_name,
|
||||||
|
defaults={'slug': tag_name.lower()}
|
||||||
|
)
|
||||||
|
tags.append(tag)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'✓ Created {len(tags)} tags'))
|
||||||
|
return tags
|
||||||
|
|
||||||
|
def create_categories(self):
|
||||||
|
"""Create categories"""
|
||||||
|
categories_data = [
|
||||||
|
{'name': 'Electronics', 'show_home': True},
|
||||||
|
{'name': 'Fashion', 'show_home': True},
|
||||||
|
{'name': 'Home & Garden', 'show_home': True},
|
||||||
|
{'name': 'Sports', 'show_home': False},
|
||||||
|
{'name': 'Toys', 'show_home': False},
|
||||||
|
{'name': 'Books', 'show_home': True},
|
||||||
|
{'name': 'Automotive', 'show_home': False},
|
||||||
|
{'name': 'Beauty', 'show_home': True},
|
||||||
|
]
|
||||||
|
|
||||||
|
categories = []
|
||||||
|
for cat_data in categories_data:
|
||||||
|
cat, created = CategoryModel.objects.get_or_create(
|
||||||
|
name=cat_data['name'],
|
||||||
|
defaults={
|
||||||
|
'show_home': cat_data['show_home'],
|
||||||
|
'category_type': AdCategoryType.PRODUCT,
|
||||||
|
'level': 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
categories.append(cat)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'✓ Created {len(categories)} categories'))
|
||||||
|
return categories
|
||||||
|
|
||||||
|
def create_users(self, count):
|
||||||
|
"""Create test users"""
|
||||||
|
users = []
|
||||||
|
|
||||||
|
# Create admin user
|
||||||
|
admin, created = User.objects.get_or_create(
|
||||||
|
username='admin',
|
||||||
|
defaults={
|
||||||
|
'email': 'admin@example.com',
|
||||||
|
'is_staff': True,
|
||||||
|
'is_superuser': True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
admin.set_password('admin123')
|
||||||
|
admin.save()
|
||||||
|
users.append(admin)
|
||||||
|
|
||||||
|
# Create regular users
|
||||||
|
for i in range(1, count):
|
||||||
|
user, created = User.objects.get_or_create(
|
||||||
|
username=f'user{i}',
|
||||||
|
defaults={
|
||||||
|
'email': f'user{i}@example.com',
|
||||||
|
'first_name': f'User',
|
||||||
|
'last_name': f'{i}',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
user.set_password('password123')
|
||||||
|
user.save()
|
||||||
|
users.append(user)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'✓ Created {len(users)} users'))
|
||||||
|
return users
|
||||||
|
|
||||||
|
def create_banners(self, categories):
|
||||||
|
"""Create banners"""
|
||||||
|
banners = []
|
||||||
|
banner_data = [
|
||||||
|
{
|
||||||
|
'title': 'Summer Sale',
|
||||||
|
'description': 'Up to 50% off on selected items',
|
||||||
|
'bg_color': '#FF5733',
|
||||||
|
'text_color': '#FFFFFF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'New Arrivals',
|
||||||
|
'description': 'Check out our latest products',
|
||||||
|
'bg_color': '#3498DB',
|
||||||
|
'text_color': '#FFFFFF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Free Shipping',
|
||||||
|
'description': 'On orders over $50',
|
||||||
|
'bg_color': '#2ECC71',
|
||||||
|
'text_color': '#FFFFFF',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, data in enumerate(banner_data):
|
||||||
|
banner = BannerModel.objects.create(
|
||||||
|
title=data['title'],
|
||||||
|
description=data['description'],
|
||||||
|
link=f'/category/{categories[i].id}/' if i < len(categories) else '/',
|
||||||
|
bg_color=data['bg_color'],
|
||||||
|
text_color=data['text_color'],
|
||||||
|
order=i,
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
banners.append(banner)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'✓ Created {len(banners)} banners'))
|
||||||
|
return banners
|
||||||
|
|
||||||
|
def create_ads(self, count, users, categories, plan, tags, colors):
|
||||||
|
"""Create ads with variants, images, and options"""
|
||||||
|
ads = []
|
||||||
|
|
||||||
|
products = [
|
||||||
|
'Smartphone', 'Laptop', 'T-Shirt', 'Jeans', 'Sofa',
|
||||||
|
'Chair', 'Football', 'Basketball', 'Book', 'Watch',
|
||||||
|
'Headphones', 'Camera', 'Shoes', 'Bag', 'Sunglasses',
|
||||||
|
'Jacket', 'Perfume', 'Toy Car', 'Bicycle', 'Tablet'
|
||||||
|
]
|
||||||
|
|
||||||
|
for i in range(count):
|
||||||
|
product_name = products[i % len(products)]
|
||||||
|
category = random.choice(categories)
|
||||||
|
user = random.choice(users)
|
||||||
|
|
||||||
|
ad = AdModel.objects.create(
|
||||||
|
user=user,
|
||||||
|
name=f'{product_name} - Model {i+1}',
|
||||||
|
ad_type=random.choice([AdType.SALE, AdType.RENT]),
|
||||||
|
category=category,
|
||||||
|
ad_category_type=AdCategoryType.PRODUCT,
|
||||||
|
_price=Decimal(random.randint(10, 1000)),
|
||||||
|
discount=Decimal(random.randint(0, 30)),
|
||||||
|
is_available=True,
|
||||||
|
physical_product=True,
|
||||||
|
plan=plan,
|
||||||
|
description=f'High quality {product_name.lower()} with amazing features. '
|
||||||
|
f'Perfect condition, great price. Limited stock available!',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add random tags
|
||||||
|
ad.tags.set(random.sample(tags, random.randint(1, 3)))
|
||||||
|
|
||||||
|
# Create variants
|
||||||
|
for color in random.sample(colors, random.randint(1, 3)):
|
||||||
|
AdVariantModel.objects.create(
|
||||||
|
ad=ad,
|
||||||
|
variant=AdVariantType.COLOR,
|
||||||
|
value=color.name,
|
||||||
|
color=color,
|
||||||
|
price=Decimal(random.randint(50, 500)),
|
||||||
|
stock_quantity=random.randint(1, 100),
|
||||||
|
is_available=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create size variants
|
||||||
|
sizes = ['S', 'M', 'L', 'XL']
|
||||||
|
for size in random.sample(sizes, random.randint(1, 3)):
|
||||||
|
AdVariantModel.objects.create(
|
||||||
|
ad=ad,
|
||||||
|
variant=AdVariantType.SIZE,
|
||||||
|
value=size,
|
||||||
|
price=Decimal(random.randint(50, 500)),
|
||||||
|
stock_quantity=random.randint(1, 100),
|
||||||
|
is_available=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create options
|
||||||
|
options = [
|
||||||
|
('Brand', random.choice(['Samsung', 'Apple', 'Sony', 'LG', 'Generic'])),
|
||||||
|
('Warranty', f'{random.choice([6, 12, 24])} months'),
|
||||||
|
('Condition', random.choice(['New', 'Like New', 'Used - Good'])),
|
||||||
|
]
|
||||||
|
for opt_name, opt_value in options:
|
||||||
|
AdOptionModel.objects.create(
|
||||||
|
ad=ad,
|
||||||
|
name=opt_name,
|
||||||
|
value=opt_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
ads.append(ad)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'✓ Created {len(ads)} ads with variants and options'))
|
||||||
|
return ads
|
||||||
|
|
||||||
|
def create_feedbacks(self, ads, users):
|
||||||
|
"""Create feedbacks for ads"""
|
||||||
|
feedbacks = []
|
||||||
|
|
||||||
|
comments = [
|
||||||
|
'Great product! Highly recommended.',
|
||||||
|
'Good quality, fast delivery.',
|
||||||
|
'Exactly as described. Very satisfied.',
|
||||||
|
'Amazing! Worth every penny.',
|
||||||
|
'Nice product but delivery was slow.',
|
||||||
|
'Excellent quality and service.',
|
||||||
|
'Very good, will buy again.',
|
||||||
|
'Perfect condition, thank you!',
|
||||||
|
'Good value for money.',
|
||||||
|
'Satisfied with the purchase.',
|
||||||
|
]
|
||||||
|
|
||||||
|
for ad in random.sample(ads, min(len(ads), 15)):
|
||||||
|
for _ in range(random.randint(1, 5)):
|
||||||
|
user = random.choice(users)
|
||||||
|
feedback = FeedbackModel.objects.create(
|
||||||
|
user=user,
|
||||||
|
ad=ad,
|
||||||
|
star=random.randint(3, 5),
|
||||||
|
comment=random.choice(comments),
|
||||||
|
)
|
||||||
|
feedbacks.append(feedback)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'✓ Created {len(feedbacks)} feedbacks'))
|
||||||
|
return feedbacks
|
||||||
|
|
||||||
|
def create_orders(self, ads, users):
|
||||||
|
"""Create orders"""
|
||||||
|
orders = []
|
||||||
|
|
||||||
|
for user in random.sample(users, min(len(users), 3)):
|
||||||
|
for _ in range(random.randint(1, 3)):
|
||||||
|
order = OrderModel.objects.create(
|
||||||
|
user=user,
|
||||||
|
status=random.choice([
|
||||||
|
OrderStatus.PENDING,
|
||||||
|
OrderStatus.PROCESSING,
|
||||||
|
OrderStatus.COMPLETED
|
||||||
|
]),
|
||||||
|
total_amount=Decimal('0.00'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add order items
|
||||||
|
total = Decimal('0.00')
|
||||||
|
for ad in random.sample(ads, random.randint(1, 3)):
|
||||||
|
quantity = random.randint(1, 3)
|
||||||
|
price = ad.price
|
||||||
|
|
||||||
|
OrderItemModel.objects.create(
|
||||||
|
order=order,
|
||||||
|
ad=ad,
|
||||||
|
price=price,
|
||||||
|
quantity=quantity,
|
||||||
|
)
|
||||||
|
total += price * quantity
|
||||||
|
|
||||||
|
order.total_amount = total
|
||||||
|
order.save()
|
||||||
|
orders.append(order)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'✓ Created {len(orders)} orders'))
|
||||||
|
return orders
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-22 11:39
|
# Generated by Django 5.2.7 on 2025-12-06 15:57
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
@@ -8,27 +11,330 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
('accounts', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Banner',
|
name='AdTopPlanModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=255, unique=True, verbose_name='Plan Name')),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Price')),
|
||||||
|
('duration', models.PositiveIntegerField(verbose_name='Duration (days)')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Ad Top Plan',
|
||||||
|
'verbose_name_plural': 'Ad Top Plans',
|
||||||
|
'db_table': 'ad_top_plan',
|
||||||
|
'ordering': ['price'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BannerModel',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
('title', models.CharField(max_length=255, verbose_name='Title')),
|
('title', models.CharField(max_length=255, verbose_name='Title')),
|
||||||
('description', models.TextField(verbose_name='Description')),
|
('description', models.TextField(verbose_name='Description')),
|
||||||
('mobile_image', models.ImageField(upload_to='banner/mobile_image/', verbose_name='Mobile Image')),
|
('mobile_image', models.ImageField(upload_to='banners/mobile/', verbose_name='Mobile Image')),
|
||||||
('desktop_image', models.ImageField(upload_to='banner/desktop_image/', verbose_name='Desktop Image')),
|
('desktop_image', models.ImageField(upload_to='banners/desktop/', verbose_name='Desktop Image')),
|
||||||
('link', models.URLField(verbose_name='Link')),
|
('link', models.URLField(verbose_name='Link')),
|
||||||
('bg_color', models.CharField(max_length=255, verbose_name='BG Color')),
|
('bg_color', models.CharField(default='#FFFFFF', max_length=7, verbose_name='Background Color')),
|
||||||
('text_color', models.CharField(max_length=255, verbose_name='Text Color')),
|
('text_color', models.CharField(default='#000000', max_length=7, verbose_name='Text Color')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
|
||||||
|
('order', models.PositiveIntegerField(default=0, verbose_name='Display Order')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Banner',
|
'verbose_name': 'Banner',
|
||||||
'verbose_name_plural': 'Banners',
|
'verbose_name_plural': 'Banners',
|
||||||
'db_table': 'banner',
|
'db_table': 'banner',
|
||||||
|
'ordering': ['order', '-created_at'],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ColorModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=255, unique=True, verbose_name='Name')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Color',
|
||||||
|
'verbose_name_plural': 'Colors',
|
||||||
|
'db_table': 'color',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TagsModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=255, unique=True, verbose_name='Tag Name')),
|
||||||
|
('slug', models.SlugField(max_length=255, unique=True, verbose_name='Slug')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Tag',
|
||||||
|
'verbose_name_plural': 'Tags',
|
||||||
|
'db_table': 'tags',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||||
|
('ad_type', models.CharField(choices=[('Buy', 'Buy'), ('Sell', 'Sell')], max_length=255, verbose_name='Type')),
|
||||||
|
('ad_category_type', models.CharField(choices=[('Product', 'Product'), ('Service', 'Service'), ('Auto', 'Auto'), ('Home', 'Home')], max_length=255, verbose_name='Category Type')),
|
||||||
|
('_price', models.DecimalField(blank=True, db_column='price', decimal_places=2, max_digits=10, null=True, verbose_name='Price')),
|
||||||
|
('discount', models.DecimalField(decimal_places=2, default=-1, max_digits=10, verbose_name='Discount')),
|
||||||
|
('is_available', models.BooleanField(default=True, verbose_name='Is available')),
|
||||||
|
('physical_product', models.BooleanField(default=False, verbose_name='Physical product')),
|
||||||
|
('image', models.ImageField(upload_to='ads/', verbose_name='Image')),
|
||||||
|
('description', models.TextField(verbose_name='Description')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ads', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.adtopplanmodel', verbose_name='Plan')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Ad',
|
||||||
|
'verbose_name_plural': 'Ads',
|
||||||
|
'db_table': 'ad',
|
||||||
|
'ordering': ['-created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdSizeModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('weight', models.PositiveIntegerField(verbose_name='Weight (g)')),
|
||||||
|
('width', models.PositiveIntegerField(verbose_name='Width (cm)')),
|
||||||
|
('height', models.PositiveIntegerField(verbose_name='Height (cm)')),
|
||||||
|
('length', models.PositiveIntegerField(verbose_name='Length (cm)')),
|
||||||
|
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='size_info', to='api.admodel', verbose_name='Ad')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Ad Size',
|
||||||
|
'verbose_name_plural': 'Ad Sizes',
|
||||||
|
'db_table': 'ad_size',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdVariantModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('value', models.CharField(max_length=255, verbose_name='Value')),
|
||||||
|
('is_available', models.BooleanField(default=True, verbose_name='Is Available')),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Price')),
|
||||||
|
('stock_quantity', models.PositiveIntegerField(default=0, verbose_name='Stock Quantity')),
|
||||||
|
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variants', to='api.admodel', verbose_name='Ad')),
|
||||||
|
('color', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.colormodel', verbose_name='Color')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Ad Variant',
|
||||||
|
'verbose_name_plural': 'Ad Variants',
|
||||||
|
'db_table': 'ad_variant',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CategoryModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='Category Name')),
|
||||||
|
('name_uz', models.CharField(max_length=255, null=True, verbose_name='Category Name')),
|
||||||
|
('name_ru', models.CharField(max_length=255, null=True, verbose_name='Category Name')),
|
||||||
|
('name_en', models.CharField(max_length=255, null=True, verbose_name='Category Name')),
|
||||||
|
('show_home', models.BooleanField(default=False, verbose_name='Show on Home')),
|
||||||
|
('level', models.IntegerField(default=0, editable=False, verbose_name='Level')),
|
||||||
|
('image', models.ImageField(blank=True, null=True, upload_to='categories/', verbose_name='Image')),
|
||||||
|
('category_type', models.CharField(choices=[('Product', 'Product'), ('Service', 'Service'), ('Auto', 'Auto'), ('Home', 'Home')], default='Product', max_length=255, verbose_name='Category Type')),
|
||||||
|
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='api.categorymodel', verbose_name='Parent Category')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Category',
|
||||||
|
'verbose_name_plural': 'Categories',
|
||||||
|
'db_table': 'category',
|
||||||
|
'ordering': ['level', 'name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='admodel',
|
||||||
|
name='category',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.categorymodel', verbose_name='Category'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='FeedbackModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('star', models.IntegerField(default=5, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)], verbose_name='Rating')),
|
||||||
|
('comment', models.TextField(max_length=1000, verbose_name='Comment')),
|
||||||
|
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='api.admodel', verbose_name='Ad')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Feedback',
|
||||||
|
'verbose_name_plural': 'Feedbacks',
|
||||||
|
'db_table': 'feedback',
|
||||||
|
'ordering': ['-created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='FeedbackImageModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('image', models.ImageField(upload_to='feedbacks/', verbose_name='Image')),
|
||||||
|
('feedback', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.feedbackmodel', verbose_name='Feedback')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Feedback Image',
|
||||||
|
'verbose_name_plural': 'Feedback Images',
|
||||||
|
'db_table': 'feedback_images',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrderModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('status', models.CharField(choices=[('Pending', 'Pending'), ('Cancel', 'Cancel'), ('Done', 'Done')], db_index=True, max_length=255, verbose_name='Status')),
|
||||||
|
('total_amount', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Total Amount')),
|
||||||
|
('address', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='accounts.address', verbose_name='Address')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Order',
|
||||||
|
'verbose_name_plural': 'Orders',
|
||||||
|
'db_table': 'order',
|
||||||
|
'ordering': ['-created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrderItemModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')),
|
||||||
|
('quantity', models.PositiveIntegerField(default=1, verbose_name='Quantity')),
|
||||||
|
('ad', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='api.admodel', verbose_name='Ad')),
|
||||||
|
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='api.ordermodel', verbose_name='Order')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Order Item',
|
||||||
|
'verbose_name_plural': 'Order Items',
|
||||||
|
'db_table': 'order_item',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='admodel',
|
||||||
|
name='tags',
|
||||||
|
field=models.ManyToManyField(blank=True, to='api.tagsmodel', verbose_name='Tags'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdOptionModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||||
|
('value', models.CharField(max_length=255, verbose_name='Value')),
|
||||||
|
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='api.admodel', verbose_name='Ad')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Ad Option',
|
||||||
|
'verbose_name_plural': 'Ad Options',
|
||||||
|
'db_table': 'ad_option',
|
||||||
|
'unique_together': {('ad', 'name')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdImageModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('image', models.ImageField(upload_to='ads/images/', verbose_name='Image')),
|
||||||
|
('order', models.PositiveIntegerField(default=0, verbose_name='Display Order')),
|
||||||
|
('is_primary', models.BooleanField(default=False, verbose_name='Is Primary')),
|
||||||
|
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.admodel', verbose_name='Ad')),
|
||||||
|
('ad_variant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.advariantmodel', verbose_name='Ad Variant')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Ad Image',
|
||||||
|
'verbose_name_plural': 'Ad Images',
|
||||||
|
'db_table': 'ad_images',
|
||||||
|
'ordering': ['order', '-created_at'],
|
||||||
|
'indexes': [models.Index(fields=['ad', 'is_primary'], name='ad_images_ad_id_ed2cb8_idx')],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='categorymodel',
|
||||||
|
index=models.Index(fields=['parent', 'show_home'], name='category_parent__ebe06e_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='categorymodel',
|
||||||
|
index=models.Index(fields=['level'], name='category_level_e67701_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='advariantmodel',
|
||||||
|
index=models.Index(fields=['ad', 'is_available'], name='ad_variant_ad_id_3a5d5e_idx'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='advariantmodel',
|
||||||
|
unique_together={('ad', 'value')},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='feedbackmodel',
|
||||||
|
index=models.Index(fields=['ad', '-created_at'], name='feedback_ad_id_444d80_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='feedbackmodel',
|
||||||
|
index=models.Index(fields=['user'], name='feedback_user_id_8cf53b_idx'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='feedbackmodel',
|
||||||
|
unique_together={('user', 'ad')},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='ordermodel',
|
||||||
|
index=models.Index(fields=['user', '-created_at'], name='order_user_id_13e363_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='ordermodel',
|
||||||
|
index=models.Index(fields=['status'], name='order_status_35c31c_idx'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='orderitemmodel',
|
||||||
|
unique_together={('order', 'ad')},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='admodel',
|
||||||
|
index=models.Index(fields=['-created_at'], name='ad_created_359de0_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='admodel',
|
||||||
|
index=models.Index(fields=['category', 'is_available'], name='ad_categor_8ff346_idx'),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,240 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-24 06:45
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0004_business_searchhistory'),
|
|
||||||
('api', '0001_initial'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AdTopPlan',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')),
|
|
||||||
('duration', models.IntegerField(verbose_name='Duration')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'AdTop Plan',
|
|
||||||
'verbose_name_plural': 'AdTop Plan',
|
|
||||||
'db_table': 'ad_top_plan',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Color',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Color',
|
|
||||||
'verbose_name_plural': 'Colors',
|
|
||||||
'db_table': 'color',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Tags',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Tags',
|
|
||||||
'verbose_name_plural': 'Tags',
|
|
||||||
'db_table': 'tags',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AdModel',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('ad_type', models.CharField(choices=[('Buy', 'Buy'), ('Sell', 'Sell')], max_length=255, verbose_name='Type')),
|
|
||||||
('ad_category_type', models.CharField(choices=[('Product', 'Product'), ('Service', 'Service'), ('Auto', 'Auto'), ('Home', 'Home')], max_length=255, verbose_name='Type')),
|
|
||||||
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Price')),
|
|
||||||
('is_available', models.BooleanField(blank=True, default=True, null=True, verbose_name='Is available')),
|
|
||||||
('physical_product', models.BooleanField(default=False, verbose_name='Physical product')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ad', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
|
||||||
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.adtopplan', verbose_name='Plan')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Ad',
|
|
||||||
'verbose_name_plural': 'Ads',
|
|
||||||
'db_table': 'ad',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AdImage',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('image', models.ImageField(upload_to='ads/images/', verbose_name='Image')),
|
|
||||||
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.admodel', verbose_name='Ad')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Ad_Image',
|
|
||||||
'verbose_name_plural': 'Ad_Images',
|
|
||||||
'db_table': 'ad_images',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AdOption',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('value', models.CharField(max_length=255, verbose_name='Value')),
|
|
||||||
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.admodel')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Ad_Option',
|
|
||||||
'verbose_name_plural': 'Ad_Options',
|
|
||||||
'db_table': 'ad_option',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AdSize',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('weight', models.PositiveIntegerField(verbose_name='Weight')),
|
|
||||||
('width', models.PositiveIntegerField(verbose_name='Width')),
|
|
||||||
('height', models.PositiveIntegerField(verbose_name='Height')),
|
|
||||||
('length', models.PositiveIntegerField(verbose_name='Length')),
|
|
||||||
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.admodel')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'AdSize',
|
|
||||||
'verbose_name_plural': 'AdSizes',
|
|
||||||
'db_table': 'ad_size',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AdVariant',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('variant', models.CharField(choices=[('Color', 'Color'), ('Size', 'Size')], db_index=True, max_length=255)),
|
|
||||||
('value', models.CharField(max_length=255)),
|
|
||||||
('is_available', models.CharField(max_length=255)),
|
|
||||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
|
||||||
('discount', models.DecimalField(decimal_places=2, default=-1, max_digits=10)),
|
|
||||||
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.admodel')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Ad_Variant',
|
|
||||||
'verbose_name_plural': 'Ad_Variants',
|
|
||||||
'db_table': 'ad_variant',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Category',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Category Name')),
|
|
||||||
('show_home', models.BooleanField(default=False, verbose_name='Show Home')),
|
|
||||||
('level', models.IntegerField(default=0, verbose_name='Level')),
|
|
||||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='api.category')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Category',
|
|
||||||
'verbose_name_plural': 'Categories',
|
|
||||||
'db_table': 'category',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='admodel',
|
|
||||||
name='category',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.category', verbose_name='Category'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Feedback',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('star', models.IntegerField(default=0, verbose_name='Star')),
|
|
||||||
('command', models.CharField(max_length=255, verbose_name='Command')),
|
|
||||||
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.admodel', verbose_name='Ad')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Feedback',
|
|
||||||
'verbose_name_plural': 'Feedbacks',
|
|
||||||
'db_table': 'feedback',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='FeedbackImages',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('image', models.ImageField(upload_to='feedback/images/', verbose_name='Image')),
|
|
||||||
('feedback', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.feedback', verbose_name='Feedback')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Feedback Images',
|
|
||||||
'verbose_name_plural': 'Feedback Images',
|
|
||||||
'db_table': 'feedback_images',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Order',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('status', models.CharField(choices=[('Pending', 'Pending'), ('Cancel', 'Cancel'), ('Done', 'Done')], max_length=255)),
|
|
||||||
('address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.address', verbose_name='Address')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Order',
|
|
||||||
'verbose_name_plural': 'Orders',
|
|
||||||
'db_table': 'order',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='OrderItem',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')),
|
|
||||||
('count', models.PositiveIntegerField(default=0, verbose_name='Count')),
|
|
||||||
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.admodel', verbose_name='Ad')),
|
|
||||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.order', verbose_name='Order')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Order Item',
|
|
||||||
'verbose_name_plural': 'Order Items',
|
|
||||||
'db_table': 'order_item',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='admodel',
|
|
||||||
name='tags',
|
|
||||||
field=models.ManyToManyField(to='api.tags', verbose_name='Tags'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-06 16:11
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SizeModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True, verbose_name='Name')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Size',
|
||||||
|
'verbose_name_plural': 'Sizes',
|
||||||
|
'db_table': 'size',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='advariantmodel',
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='advariantmodel',
|
||||||
|
name='color',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='api.colormodel', verbose_name='Color'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='advariantmodel',
|
||||||
|
name='size',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='api.sizemodel', verbose_name='Size'),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='advariantmodel',
|
||||||
|
name='value',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-24 10:15
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0002_adtopplan_color_tags_admodel_adimage_adoption_adsize_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='category',
|
|
||||||
name='image',
|
|
||||||
field=models.ImageField(blank=True, null=True, upload_to='', verbose_name='Image'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
18
core/apps/api/migrations/0003_colormodel_color.py
Normal file
18
core/apps/api/migrations/0003_colormodel_color.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-06 16:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0002_sizemodel_alter_advariantmodel_unique_together_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='colormodel',
|
||||||
|
name='color',
|
||||||
|
field=models.CharField(max_length=255, null=True, verbose_name='Color'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-25 07:14
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0003_category_image'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='category',
|
|
||||||
name='category_type',
|
|
||||||
field=models.CharField(choices=[('Product', 'Product'), ('Service', 'Service'), ('Auto', 'Auto'), ('Home', 'Home')], default='Product', max_length=255, verbose_name='Category Type'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-25 10:10
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0004_category_category_type'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='admodel',
|
|
||||||
name='star',
|
|
||||||
field=models.FloatField(default=0.0, verbose_name='Star'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-25 10:30
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0005_admodel_star'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='adimage',
|
|
||||||
name='ad',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.admodel', verbose_name='Ad'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-25 10:35
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0006_alter_adimage_ad'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='advariant',
|
|
||||||
name='ad',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variants', to='api.admodel'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-25 10:43
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0007_alter_advariant_ad'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='adimage',
|
|
||||||
name='ad_variant',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.advariant', verbose_name='Ad Variant'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-25 10:45
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0008_adimage_ad_variant'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='adimage',
|
|
||||||
name='ad_variant',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.advariant', verbose_name='Ad Variant'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-25 11:00
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0009_alter_adimage_ad_variant'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='admodel',
|
|
||||||
name='star',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='admodel',
|
|
||||||
name='image',
|
|
||||||
field=models.ImageField(default=1, upload_to='', verbose_name='Image'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-25 11:23
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0010_remove_admodel_star_admodel_image'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='feedback',
|
|
||||||
name='ad',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to='api.admodel', verbose_name='Ad'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-25 11:38
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0011_alter_feedback_ad'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='feedback',
|
|
||||||
old_name='command',
|
|
||||||
new_name='comment',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-26 10:04
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0012_rename_command_feedback_comment'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='feedback',
|
|
||||||
name='comment',
|
|
||||||
field=models.CharField(max_length=255, verbose_name='Comment'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-28 11:21
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0013_alter_feedback_comment'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='admodel',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(default=1, verbose_name='Description'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-27 07:55
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0013_alter_feedback_comment'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='category',
|
|
||||||
name='name_en',
|
|
||||||
field=models.CharField(max_length=255, null=True, verbose_name='Category Name'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='category',
|
|
||||||
name='name_ru',
|
|
||||||
field=models.CharField(max_length=255, null=True, verbose_name='Category Name'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='category',
|
|
||||||
name='name_uz',
|
|
||||||
field=models.CharField(max_length=255, null=True, verbose_name='Category Name'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-28 11:45
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0014_admodel_description'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='adoption',
|
|
||||||
name='ad',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='api.admodel', verbose_name='Ad'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-02 12:32
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0014_category_name_en_category_name_ru_category_name_uz'),
|
|
||||||
('api', '0015_alter_adoption_ad'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
]
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from .banner import * # noqa
|
from .banner import *
|
||||||
from .feedback import * # noqa
|
from .feedback import *
|
||||||
from .ad import * # noqa
|
from .ad import *
|
||||||
from .ad_items import * # noqa
|
from .common import *
|
||||||
from .order import * # noqa
|
from .order import *
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
from .ad import * # noqa
|
from .ad import *
|
||||||
from .category import * # noqa
|
from .category import *
|
||||||
|
from .variant import *
|
||||||
|
from .image import *
|
||||||
|
from .option import *
|
||||||
|
from .size import *
|
||||||
|
|||||||
@@ -1,45 +1,43 @@
|
|||||||
|
# type: ignore
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django_core.models.base import AbstractBaseModel
|
from django_core.models.base import AbstractBaseModel
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from core.apps.api.choices.ad_type import AdType, AdCategoryType
|
from core.apps.api.choices.ad_type import AdType, AdCategoryType
|
||||||
from model_bakery import baker
|
|
||||||
|
|
||||||
|
|
||||||
class AdModel(AbstractBaseModel):
|
class AdModel(AbstractBaseModel):
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"), related_name="ad")
|
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"), related_name="ads")
|
||||||
name = models.CharField(verbose_name=_("Name"), max_length=255)
|
name = models.CharField(_("Name"), max_length=255)
|
||||||
ad_type = models.CharField(verbose_name=_("Type"), max_length=255, choices=AdType)
|
ad_type = models.CharField(_("Type"), max_length=255, choices=AdType)
|
||||||
category = models.ForeignKey("api.Category", on_delete=models.CASCADE, verbose_name=_("Category"))
|
category = models.ForeignKey("api.CategoryModel", on_delete=models.CASCADE, verbose_name=_("Category"))
|
||||||
ad_category_type = models.CharField(verbose_name=_("Type"), max_length=255, choices=AdCategoryType)
|
ad_category_type = models.CharField(_("Category Type"), max_length=255, choices=AdCategoryType)
|
||||||
price = models.DecimalField(verbose_name=_("Price"), max_digits=10, decimal_places=2, null=True, blank=True)
|
_price = models.DecimalField(_("Price"), max_digits=10, decimal_places=2, null=True, blank=True, db_column="price")
|
||||||
is_available = models.BooleanField(verbose_name=_("Is available"), default=True, blank=True, null=True)
|
discount = models.DecimalField(_("Discount"), max_digits=10, decimal_places=2, default=-1)
|
||||||
physical_product = models.BooleanField(verbose_name=_("Physical product"), default=False)
|
is_available = models.BooleanField(_("Is available"), default=True)
|
||||||
plan = models.ForeignKey("api.AdTopPlan", on_delete=models.CASCADE, verbose_name=_("Plan"))
|
physical_product = models.BooleanField(_("Physical product"), default=False)
|
||||||
tags = models.ManyToManyField("api.Tags", verbose_name=_("Tags"))
|
plan = models.ForeignKey("api.AdTopPlanModel", on_delete=models.CASCADE, verbose_name=_("Plan"))
|
||||||
image = models.ImageField(verbose_name=_("Image"))
|
tags = models.ManyToManyField("api.TagsModel", verbose_name=_("Tags"), blank=True)
|
||||||
description = models.TextField(verbose_name=_("Description"))
|
image = models.ImageField(_("Image"), upload_to="ads/")
|
||||||
|
description = models.TextField(_("Description"))
|
||||||
@classmethod
|
|
||||||
def _baker(cls):
|
|
||||||
return baker.make(cls)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def price(self):
|
||||||
|
"""Get actual price - either from variant or direct price"""
|
||||||
|
if self.ad_category_type == AdCategoryType.PRODUCT.value:
|
||||||
|
variant = self.variants.order_by("price").first()
|
||||||
|
return variant.price if variant else 0
|
||||||
|
return self._price
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "ad"
|
db_table = "ad"
|
||||||
verbose_name = _("Ad")
|
verbose_name = _("Ad")
|
||||||
verbose_name_plural = _("Ads")
|
verbose_name_plural = _("Ads")
|
||||||
|
ordering = ["-created_at"]
|
||||||
|
indexes = [
|
||||||
class Color(AbstractBaseModel):
|
models.Index(fields=["-created_at"]),
|
||||||
name = models.CharField(verbose_name=_("Name"), max_length=255)
|
models.Index(fields=["category", "is_available"]),
|
||||||
|
]
|
||||||
def __str__(self):
|
|
||||||
return str(self.pk)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = "color"
|
|
||||||
verbose_name = _("Color")
|
|
||||||
verbose_name_plural = _("Colors")
|
|
||||||
|
|||||||
@@ -2,26 +2,45 @@ from django.db import models
|
|||||||
from django_core.models.base import AbstractBaseModel
|
from django_core.models.base import AbstractBaseModel
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from core.apps.api.choices import AdCategoryType
|
from core.apps.api.choices import AdCategoryType
|
||||||
from model_bakery import baker
|
|
||||||
|
|
||||||
|
|
||||||
class Category(AbstractBaseModel):
|
class CategoryModel(AbstractBaseModel):
|
||||||
name = models.CharField(max_length=255, verbose_name=_('Category Name'))
|
name = models.CharField(_("Category Name"), max_length=255)
|
||||||
parent = models.ForeignKey('self', null=True, blank=True, related_name='children', on_delete=models.CASCADE)
|
parent = models.ForeignKey(
|
||||||
show_home = models.BooleanField(default=False, verbose_name=_('Show Home'))
|
"self",
|
||||||
level = models.IntegerField(default=0, verbose_name=_('Level'))
|
null=True,
|
||||||
image = models.ImageField(verbose_name=_('Image'), null=True, blank=True)
|
blank=True,
|
||||||
category_type = models.CharField(max_length=255, verbose_name=_('Category Type'), choices=AdCategoryType,
|
related_name="children",
|
||||||
default=AdCategoryType.PRODUCT)
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Parent Category")
|
||||||
@classmethod
|
)
|
||||||
def _baker(cls):
|
show_home = models.BooleanField(_("Show on Home"), default=False)
|
||||||
return baker.make(cls)
|
level = models.IntegerField(_("Level"), default=0, editable=False)
|
||||||
|
image = models.ImageField(_("Image"), upload_to="categories/", null=True, blank=True)
|
||||||
|
category_type = models.CharField(
|
||||||
|
_("Category Type"),
|
||||||
|
max_length=255,
|
||||||
|
choices=AdCategoryType,
|
||||||
|
default=AdCategoryType.PRODUCT
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""Auto-calculate level based on parent"""
|
||||||
|
if self.parent:
|
||||||
|
self.level = self.parent.level + 1
|
||||||
|
else:
|
||||||
|
self.level = 0
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'category'
|
db_table = "category"
|
||||||
verbose_name = _('Category')
|
verbose_name = _("Category")
|
||||||
verbose_name_plural = _('Categories')
|
verbose_name_plural = _("Categories")
|
||||||
|
ordering = ["level", "name"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["parent", "show_home"]),
|
||||||
|
models.Index(fields=["level"]),
|
||||||
|
]
|
||||||
|
|||||||
35
core/apps/api/models/ad/image.py
Normal file
35
core/apps/api/models/ad/image.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class AdImageModel(AbstractBaseModel):
|
||||||
|
ad = models.ForeignKey(
|
||||||
|
"api.AdModel",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Ad"),
|
||||||
|
related_name="images"
|
||||||
|
)
|
||||||
|
ad_variant = models.ForeignKey(
|
||||||
|
"api.AdVariantModel",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Ad Variant"),
|
||||||
|
related_name="images",
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
image = models.ImageField(_("Image"), upload_to="ads/images/")
|
||||||
|
order = models.PositiveIntegerField(_("Display Order"), default=0)
|
||||||
|
is_primary = models.BooleanField(_("Is Primary"), default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Image for {self.ad.name}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ad_images"
|
||||||
|
verbose_name = _("Ad Image")
|
||||||
|
verbose_name_plural = _("Ad Images")
|
||||||
|
ordering = ["order", "-created_at"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["ad", "is_primary"]),
|
||||||
|
]
|
||||||
23
core/apps/api/models/ad/option.py
Normal file
23
core/apps/api/models/ad/option.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class AdOptionModel(AbstractBaseModel):
|
||||||
|
ad = models.ForeignKey(
|
||||||
|
"api.AdModel",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="options",
|
||||||
|
verbose_name=_("Ad")
|
||||||
|
)
|
||||||
|
name = models.CharField(_("Name"), max_length=255)
|
||||||
|
value = models.CharField(_("Value"), max_length=255)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name}: {self.value}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ad_option"
|
||||||
|
verbose_name = _("Ad Option")
|
||||||
|
verbose_name_plural = _("Ad Options")
|
||||||
|
unique_together = [["ad", "name"]]
|
||||||
24
core/apps/api/models/ad/size.py
Normal file
24
core/apps/api/models/ad/size.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class AdSizeModel(AbstractBaseModel):
|
||||||
|
ad = models.ForeignKey(
|
||||||
|
"api.AdModel",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="size_info",
|
||||||
|
verbose_name=_("Ad")
|
||||||
|
)
|
||||||
|
weight = models.PositiveIntegerField(_("Weight (g)"))
|
||||||
|
width = models.PositiveIntegerField(_("Width (cm)"))
|
||||||
|
height = models.PositiveIntegerField(_("Height (cm)"))
|
||||||
|
length = models.PositiveIntegerField(_("Length (cm)"))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.width}x{self.height}x{self.length}cm, {self.weight}g"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ad_size"
|
||||||
|
verbose_name = _("Ad Size")
|
||||||
|
verbose_name_plural = _("Ad Sizes")
|
||||||
26
core/apps/api/models/ad/variant.py
Normal file
26
core/apps/api/models/ad/variant.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
|
|
||||||
|
class AdVariantModel(AbstractBaseModel):
|
||||||
|
ad = models.ForeignKey("api.AdModel", on_delete=models.CASCADE, related_name="variants", verbose_name=_("Ad"))
|
||||||
|
color = models.ForeignKey(
|
||||||
|
"api.ColorModel", on_delete=models.CASCADE, verbose_name=_("Color"), null=True, blank=False
|
||||||
|
)
|
||||||
|
size = models.ForeignKey("api.SizeModel", on_delete=models.CASCADE, verbose_name=_("Size"), null=True, blank=False)
|
||||||
|
is_available = models.BooleanField(_("Is Available"), default=True)
|
||||||
|
price = models.DecimalField(_("Price"), max_digits=10, decimal_places=2, validators=[MinValueValidator(0)])
|
||||||
|
stock_quantity = models.PositiveIntegerField(_("Stock Quantity"), default=0)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.color} - {self.size}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ad_variant"
|
||||||
|
verbose_name = _("Ad Variant")
|
||||||
|
verbose_name_plural = _("Ad Variants")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["ad", "is_available"]),
|
||||||
|
]
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from .ad_top_plan import * # noqa
|
|
||||||
from .tags import * # noqa
|
|
||||||
from .ad_images import * # noqa
|
|
||||||
from .ad_option import * # noqa
|
|
||||||
from .ad_size import * # noqa
|
|
||||||
from .ad_variant import * # noqa
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django_core.models.base import AbstractBaseModel
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from core.apps.api.models import AdModel
|
|
||||||
|
|
||||||
|
|
||||||
class AdImage(AbstractBaseModel):
|
|
||||||
image = models.ImageField(verbose_name=_("Image"), upload_to="ads/images/")
|
|
||||||
ad = models.ForeignKey(AdModel, verbose_name=_("Ad"), related_name="images",
|
|
||||||
on_delete=models.CASCADE)
|
|
||||||
ad_variant = models.ForeignKey("api.AdVariant", verbose_name=_("Ad Variant"), null=True, blank=True,
|
|
||||||
related_name="images",
|
|
||||||
on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.pk)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = "ad_images"
|
|
||||||
verbose_name = _("Ad_Image")
|
|
||||||
verbose_name_plural = _("Ad_Images")
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django_core.models.base import AbstractBaseModel
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from core.apps.api.models import AdModel
|
|
||||||
|
|
||||||
|
|
||||||
class AdOption(AbstractBaseModel):
|
|
||||||
name = models.CharField(_("Name"), max_length=255)
|
|
||||||
value = models.CharField(_("Value"), max_length=255)
|
|
||||||
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, related_name="options", verbose_name=_("Ad"))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.pk)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = "ad_option"
|
|
||||||
verbose_name = _("Ad_Option")
|
|
||||||
verbose_name_plural = _("Ad_Options")
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django_core.models.base import AbstractBaseModel
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from core.apps.api.models import AdModel
|
|
||||||
|
|
||||||
|
|
||||||
class AdSize(AbstractBaseModel):
|
|
||||||
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE)
|
|
||||||
weight = models.PositiveIntegerField(verbose_name=_("Weight"))
|
|
||||||
width = models.PositiveIntegerField(verbose_name=_("Width"))
|
|
||||||
height = models.PositiveIntegerField(verbose_name=_("Height"))
|
|
||||||
length = models.PositiveIntegerField(verbose_name=_("Length"))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.pk)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = "ad_size"
|
|
||||||
verbose_name = _("AdSize")
|
|
||||||
verbose_name_plural = _("AdSizes")
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django_core.models.base import AbstractBaseModel
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class AdTopPlan(AbstractBaseModel):
|
|
||||||
name = models.CharField(verbose_name=_('Name'), max_length=255)
|
|
||||||
price = models.DecimalField(verbose_name=_('Price'), max_digits=10, decimal_places=2)
|
|
||||||
duration = models.IntegerField(verbose_name=_('Duration'))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.pk)
|
|
||||||
class Meta:
|
|
||||||
db_table = 'ad_top_plan'
|
|
||||||
verbose_name = _('AdTop Plan')
|
|
||||||
verbose_name_plural = _('AdTop Plan')
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django_core.models.base import AbstractBaseModel
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from core.apps.api.models import AdModel
|
|
||||||
from core.apps.api.choices.ad_variant_type import AdVariantType
|
|
||||||
|
|
||||||
|
|
||||||
class AdVariant(AbstractBaseModel):
|
|
||||||
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, related_name="variants")
|
|
||||||
variant = models.CharField(max_length=255, choices=AdVariantType, db_index=True)
|
|
||||||
value = models.CharField(max_length=255)
|
|
||||||
is_available = models.CharField(max_length=255)
|
|
||||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
||||||
discount = models.DecimalField(max_digits=10, decimal_places=2, default=-1)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.pk)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = "ad_variant"
|
|
||||||
verbose_name = _("Ad_Variant")
|
|
||||||
verbose_name_plural = _("Ad_Variants")
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django_core.models.base import AbstractBaseModel
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class Tags(AbstractBaseModel):
|
|
||||||
name = models.CharField(verbose_name=_("Name"), max_length=255)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.pk)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = 'tags'
|
|
||||||
verbose_name = _("Tags")
|
|
||||||
verbose_name_plural = _("Tags")
|
|
||||||
@@ -1 +1 @@
|
|||||||
from .banner import * # noqa
|
from .banner import *
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_core.models.base import AbstractBaseModel
|
from django_core.models.base import AbstractBaseModel
|
||||||
from model_bakery import baker
|
|
||||||
|
|
||||||
|
|
||||||
class Banner(AbstractBaseModel):
|
class BannerModel(AbstractBaseModel):
|
||||||
title = models.CharField(max_length=255, verbose_name=_("Title"))
|
title = models.CharField(_("Title"), max_length=255)
|
||||||
description = models.TextField(verbose_name=_("Description"))
|
description = models.TextField(_("Description"))
|
||||||
mobile_image = models.ImageField(verbose_name=_("Mobile Image"), upload_to="banner/mobile_image/")
|
mobile_image = models.ImageField(
|
||||||
desktop_image = models.ImageField(verbose_name=_("Desktop Image"), upload_to="banner/desktop_image/")
|
_("Mobile Image"),
|
||||||
link = models.URLField(verbose_name=_("Link"))
|
upload_to="banners/mobile/"
|
||||||
bg_color = models.CharField(verbose_name=_("BG Color"), max_length=255)
|
)
|
||||||
text_color = models.CharField(verbose_name=_("Text Color"), max_length=255)
|
desktop_image = models.ImageField(
|
||||||
|
_("Desktop Image"),
|
||||||
@classmethod
|
upload_to="banners/desktop/"
|
||||||
def _baker(cls):
|
)
|
||||||
return baker.make(cls)
|
link = models.URLField(_("Link"))
|
||||||
|
bg_color = models.CharField(_("Background Color"), max_length=7, default="#FFFFFF")
|
||||||
|
text_color = models.CharField(_("Text Color"), max_length=7, default="#000000")
|
||||||
|
is_active = models.BooleanField(_("Is Active"), default=True)
|
||||||
|
order = models.PositiveIntegerField(_("Display Order"), default=0)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return self.title
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "banner"
|
db_table = "banner"
|
||||||
verbose_name = _("Banner")
|
verbose_name = _("Banner")
|
||||||
verbose_name_plural = _("Banners")
|
verbose_name_plural = _("Banners")
|
||||||
|
ordering = ["order", "-created_at"]
|
||||||
|
|||||||
4
core/apps/api/models/common/__init__.py
Normal file
4
core/apps/api/models/common/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from .tags import *
|
||||||
|
from .plan import *
|
||||||
|
from .size import * # noqa
|
||||||
|
from .color import * # noqa
|
||||||
17
core/apps/api/models/common/color.py
Normal file
17
core/apps/api/models/common/color.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class ColorModel(AbstractBaseModel):
|
||||||
|
name = models.CharField(_("Name"), max_length=255, unique=True)
|
||||||
|
color = models.CharField(_("Color"), max_length=255, null=True, blank=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "color"
|
||||||
|
verbose_name = _("Color")
|
||||||
|
verbose_name_plural = _("Colors")
|
||||||
|
ordering = ["name"]
|
||||||
26
core/apps/api/models/common/plan.py
Normal file
26
core/apps/api/models/common/plan.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
|
|
||||||
|
class AdTopPlanModel(AbstractBaseModel):
|
||||||
|
name = models.CharField(_("Plan Name"), max_length=255, unique=True)
|
||||||
|
price = models.DecimalField(
|
||||||
|
_("Price"),
|
||||||
|
max_digits=10,
|
||||||
|
decimal_places=2,
|
||||||
|
validators=[MinValueValidator(0)]
|
||||||
|
)
|
||||||
|
duration = models.PositiveIntegerField(_("Duration (days)"))
|
||||||
|
description = models.TextField(_("Description"), blank=True)
|
||||||
|
is_active = models.BooleanField(_("Is Active"), default=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} - {self.duration} days"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ad_top_plan"
|
||||||
|
verbose_name = _("Ad Top Plan")
|
||||||
|
verbose_name_plural = _("Ad Top Plans")
|
||||||
|
ordering = ["price"]
|
||||||
16
core/apps/api/models/common/size.py
Normal file
16
core/apps/api/models/common/size.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class SizeModel(AbstractBaseModel):
|
||||||
|
name = models.CharField(_("Name"), max_length=100, unique=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "size"
|
||||||
|
verbose_name = _("Size")
|
||||||
|
verbose_name_plural = _("Sizes")
|
||||||
|
ordering = ["name"]
|
||||||
17
core/apps/api/models/common/tags.py
Normal file
17
core/apps/api/models/common/tags.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class TagsModel(AbstractBaseModel):
|
||||||
|
name = models.CharField(_("Tag Name"), max_length=255, unique=True)
|
||||||
|
slug = models.SlugField(_("Slug"), max_length=255, unique=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "tags"
|
||||||
|
verbose_name = _("Tag")
|
||||||
|
verbose_name_plural = _("Tags")
|
||||||
|
ordering = ["name"]
|
||||||
@@ -1 +1 @@
|
|||||||
from .feedback import * # noqa
|
from .feedback import *
|
||||||
|
|||||||
@@ -2,33 +2,57 @@ from django.db import models
|
|||||||
from django_core.models.base import AbstractBaseModel
|
from django_core.models.base import AbstractBaseModel
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from core.apps.api.models.ad import AdModel
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
|
|
||||||
|
|
||||||
class Feedback(AbstractBaseModel):
|
class FeedbackModel(AbstractBaseModel):
|
||||||
star = models.IntegerField(default=0, verbose_name=_("Star"))
|
star = models.IntegerField(
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"))
|
_("Rating"),
|
||||||
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad"), related_name="feedback")
|
default=5,
|
||||||
comment = models.CharField(max_length=255, verbose_name=_("Comment"))
|
validators=[MinValueValidator(1), MaxValueValidator(5)]
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
get_user_model(),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("User"),
|
||||||
|
related_name="feedbacks"
|
||||||
|
)
|
||||||
|
ad = models.ForeignKey(
|
||||||
|
"api.AdModel",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Ad"),
|
||||||
|
related_name="feedbacks"
|
||||||
|
)
|
||||||
|
comment = models.TextField(_("Comment"), max_length=1000)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return f"{self.user.username} - {self.ad.name} ({self.star}★)"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "feedback"
|
db_table = "feedback"
|
||||||
verbose_name = _("Feedback")
|
verbose_name = _("Feedback")
|
||||||
verbose_name_plural = _("Feedbacks")
|
verbose_name_plural = _("Feedbacks")
|
||||||
|
ordering = ["-created_at"]
|
||||||
|
unique_together = [["user", "ad"]]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["ad", "-created_at"]),
|
||||||
|
models.Index(fields=["user"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class FeedbackImages(AbstractBaseModel):
|
class FeedbackImageModel(AbstractBaseModel):
|
||||||
feedback = models.ForeignKey(Feedback, on_delete=models.CASCADE, verbose_name=_("Feedback"))
|
feedback = models.ForeignKey(
|
||||||
image = models.ImageField(verbose_name=_("Image"), upload_to="feedback/"
|
FeedbackModel,
|
||||||
"images/")
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Feedback"),
|
||||||
|
related_name="images"
|
||||||
|
)
|
||||||
|
image = models.ImageField(_("Image"), upload_to="feedbacks/")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return f"Image for {self.feedback}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "feedback_images"
|
db_table = "feedback_images"
|
||||||
verbose_name = _("Feedback Images")
|
verbose_name = _("Feedback Image")
|
||||||
verbose_name_plural = _("Feedback Images")
|
verbose_name_plural = _("Feedback Images")
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from .order import * # noqa
|
from .order import *
|
||||||
|
|||||||
@@ -4,33 +4,82 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from core.apps.api.choices import OrderStatus
|
from core.apps.api.choices import OrderStatus
|
||||||
from core.apps.accounts.models import Address
|
from core.apps.accounts.models import Address
|
||||||
from core.apps.api.models import AdModel
|
|
||||||
|
|
||||||
|
|
||||||
class Order(AbstractBaseModel):
|
class OrderModel(AbstractBaseModel):
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"))
|
user = models.ForeignKey(
|
||||||
status = models.CharField(max_length=255, choices=OrderStatus)
|
get_user_model(),
|
||||||
address = models.ForeignKey(Address, on_delete=models.CASCADE, verbose_name=_("Address"))
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("User"),
|
||||||
|
related_name="orders"
|
||||||
|
)
|
||||||
|
status = models.CharField(
|
||||||
|
_("Status"),
|
||||||
|
max_length=255,
|
||||||
|
choices=OrderStatus,
|
||||||
|
db_index=True
|
||||||
|
)
|
||||||
|
address = models.ForeignKey(
|
||||||
|
Address,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Address")
|
||||||
|
)
|
||||||
|
total_amount = models.DecimalField(
|
||||||
|
_("Total Amount"),
|
||||||
|
max_digits=10,
|
||||||
|
decimal_places=2,
|
||||||
|
default=0
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return f"Order #{self.pk} - {self.user.username}"
|
||||||
|
|
||||||
|
def calculate_total(self):
|
||||||
|
"""Calculate total from order items"""
|
||||||
|
total = sum(item.subtotal for item in self.items.all())
|
||||||
|
self.total_amount = total
|
||||||
|
return total
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "order"
|
db_table = "order"
|
||||||
verbose_name = _("Order")
|
verbose_name = _("Order")
|
||||||
verbose_name_plural = _("Orders")
|
verbose_name_plural = _("Orders")
|
||||||
|
ordering = ["-created_at"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["user", "-created_at"]),
|
||||||
|
models.Index(fields=["status"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class OrderItem(models.Model):
|
class OrderItemModel(AbstractBaseModel):
|
||||||
order = models.ForeignKey(Order, on_delete=models.CASCADE, verbose_name=_("Order"))
|
order = models.ForeignKey(
|
||||||
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("Price"))
|
OrderModel,
|
||||||
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad"))
|
on_delete=models.CASCADE,
|
||||||
count = models.PositiveIntegerField(default=0, verbose_name=_("Count"))
|
verbose_name=_("Order"),
|
||||||
|
related_name="items"
|
||||||
|
)
|
||||||
|
ad = models.ForeignKey(
|
||||||
|
"api.AdModel",
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Ad")
|
||||||
|
)
|
||||||
|
price = models.DecimalField(
|
||||||
|
_("Price"),
|
||||||
|
max_digits=10,
|
||||||
|
decimal_places=2
|
||||||
|
)
|
||||||
|
quantity = models.PositiveIntegerField(_("Quantity"), default=1)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return f"{self.ad.name} x {self.quantity}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subtotal(self):
|
||||||
|
"""Calculate item subtotal"""
|
||||||
|
return self.price * self.quantity
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "order_item"
|
db_table = "order_item"
|
||||||
verbose_name = _("Order Item")
|
verbose_name = _("Order Item")
|
||||||
verbose_name_plural = _("Order Items")
|
verbose_name_plural = _("Order Items")
|
||||||
|
unique_together = [["order", "ad"]]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from .category import * # noqa
|
from .ad import *
|
||||||
from .search import * # noqa
|
from .banner import *
|
||||||
from .ad import * # noqa
|
from .category import *
|
||||||
from .user import * # noqa
|
from .common import * # noqa
|
||||||
from .notification import * # noqa
|
from .notification import *
|
||||||
from .banner import * # noqa
|
from .search import *
|
||||||
|
from .user import *
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
from .home_api import * # noqa
|
from .home_api import *
|
||||||
from .ad import * # noqa
|
from .ad import *
|
||||||
|
|||||||
@@ -1,270 +1,148 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.db.models import Avg
|
from django.db.models import Avg
|
||||||
|
|
||||||
from core.apps.accounts.choices import AccountType
|
from core.apps.accounts.choices import AccountType
|
||||||
from core.apps.api.models import AdModel, AdVariant, Category, AdImage, AdOption
|
from core.apps.api.models import AdModel, AdVariantModel, CategoryModel, AdImageModel, AdOptionModel
|
||||||
from core.apps.accounts.models import UserLike
|
from core.apps.accounts.models import UserLike
|
||||||
from core.apps.api.choices import AdVariantType
|
from core.apps.api.choices import AdVariantType
|
||||||
|
from core.apps.api.serializers.common.color import ListColorSerializer
|
||||||
|
from core.apps.api.serializers.common.size import ListSizeSerializer
|
||||||
|
|
||||||
|
|
||||||
class AdOptionSerializer(serializers.ModelSerializer):
|
class AdOptionSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdOption
|
model = AdOptionModel
|
||||||
fields = [
|
fields = ["id", "name", "value"]
|
||||||
"id",
|
read_only_fields = ["id"]
|
||||||
"name",
|
|
||||||
"value",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class CategorySerializer(serializers.ModelSerializer):
|
class CategorySerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Category
|
model = CategoryModel
|
||||||
fields = ["id", "name"]
|
fields = ["id", "name"]
|
||||||
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
class AdImageSerializer(serializers.ModelSerializer):
|
class AdImageSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdImage
|
model = AdImageModel
|
||||||
fields = [
|
fields = ["id", "image", "ad_variant", "is_primary", "order"]
|
||||||
"image",
|
read_only_fields = ["id"]
|
||||||
"ad_variant"
|
|
||||||
]
|
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
data = super().to_representation(instance)
|
data = super().to_representation(instance)
|
||||||
|
if not instance.ad_variant:
|
||||||
if instance.ad_variant is None:
|
|
||||||
data.pop("ad_variant", None)
|
data.pop("ad_variant", None)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class AdVariantSerializer(serializers.ModelSerializer):
|
class AdVariantSerializer(serializers.ModelSerializer):
|
||||||
|
color = ListColorSerializer()
|
||||||
|
size = ListSizeSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdVariant
|
model = AdVariantModel
|
||||||
fields = [
|
fields = ["id", "size", "color", "is_available", "price", "stock_quantity"]
|
||||||
"id",
|
read_only_fields = ["id", "color_name"]
|
||||||
"variant",
|
|
||||||
"value",
|
|
||||||
"is_available",
|
|
||||||
"price",
|
|
||||||
"discount",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAdSerializer(serializers.ModelSerializer):
|
class AdListSerializer(serializers.ModelSerializer):
|
||||||
is_liked = serializers.SerializerMethodField()
|
is_liked = serializers.SerializerMethodField()
|
||||||
star = serializers.SerializerMethodField()
|
rating = serializers.SerializerMethodField()
|
||||||
comment_count = serializers.SerializerMethodField()
|
reviews_count = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdModel
|
model = AdModel
|
||||||
fields = [
|
fields = ["id", "name", "price", "image", "discount", "is_liked", "rating", "reviews_count", "is_available"]
|
||||||
"id",
|
read_only_fields = fields
|
||||||
"name",
|
|
||||||
"price",
|
|
||||||
"image",
|
|
||||||
"is_liked",
|
|
||||||
"star",
|
|
||||||
"comment_count",
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_star(self, obj):
|
def get_rating(self, obj):
|
||||||
avg = obj.feedback.aggregate(avg=Avg("star"))["avg"]
|
"""Get average rating from feedbacks"""
|
||||||
return avg or 0
|
avg = obj.feedbacks.aggregate(avg=Avg("star"))["avg"]
|
||||||
|
return round(avg, 1) if avg else 0
|
||||||
|
|
||||||
def get_comment_count(self, obj):
|
def get_reviews_count(self, obj):
|
||||||
count = obj.feedback.count()
|
"""Get total count of feedbacks"""
|
||||||
return count or 0
|
return obj.feedbacks.count()
|
||||||
|
|
||||||
def get_is_liked(self, obj):
|
def get_is_liked(self, obj):
|
||||||
|
"""Check if current user liked this ad"""
|
||||||
request = self.context.get("request")
|
request = self.context.get("request")
|
||||||
user = getattr(request, "user", None)
|
if not request or not request.user.is_authenticated:
|
||||||
|
|
||||||
if not user or not user.is_authenticated:
|
|
||||||
return False
|
return False
|
||||||
|
return UserLike.objects.filter(user=request.user, ad=obj).exists()
|
||||||
return UserLike.objects.filter(user=user, ad=obj).exists()
|
|
||||||
|
|
||||||
|
|
||||||
class ListAdSerializer(BaseAdSerializer):
|
class AdDetailSerializer(AdListSerializer):
|
||||||
price = serializers.SerializerMethodField()
|
|
||||||
discount = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta(BaseAdSerializer.Meta):
|
|
||||||
fields = [
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"price",
|
|
||||||
"image",
|
|
||||||
"star",
|
|
||||||
"comment_count",
|
|
||||||
"discount",
|
|
||||||
"is_liked",
|
|
||||||
]
|
|
||||||
|
|
||||||
def _get_first_variant(self, obj):
|
|
||||||
if not hasattr(self, "_variant_cache"):
|
|
||||||
self._variant_cache = {}
|
|
||||||
if obj.id not in self._variant_cache:
|
|
||||||
self._variant_cache[obj.id] = obj.variants.order_by("price").first()
|
|
||||||
return self._variant_cache[obj.id]
|
|
||||||
|
|
||||||
def get_price(self, obj):
|
|
||||||
variant = self._get_first_variant(obj)
|
|
||||||
if not variant:
|
|
||||||
return obj.price
|
|
||||||
return variant.price if variant else 0
|
|
||||||
|
|
||||||
def get_discount(self, obj):
|
|
||||||
variant = self._get_first_variant(obj)
|
|
||||||
return variant.discount if variant else -1.0
|
|
||||||
|
|
||||||
|
|
||||||
class FullListAdSerializer(serializers.Serializer):
|
|
||||||
ads = ListAdSerializer(many=True)
|
|
||||||
categories = serializers.SerializerMethodField()
|
|
||||||
colors = serializers.SerializerMethodField()
|
|
||||||
sizes = serializers.SerializerMethodField()
|
|
||||||
min_price = serializers.SerializerMethodField()
|
|
||||||
max_price = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
def get_categories(self, obj):
|
|
||||||
ads = obj.get("ads", [])
|
|
||||||
|
|
||||||
category_ids = set()
|
|
||||||
categories = []
|
|
||||||
|
|
||||||
for ad in ads:
|
|
||||||
category = ad.category
|
|
||||||
if category and category.id not in category_ids:
|
|
||||||
category_ids.add(category.id)
|
|
||||||
categories.append(category)
|
|
||||||
|
|
||||||
return CategorySerializer(categories, many=True).data
|
|
||||||
|
|
||||||
def get_colors(self, obj):
|
|
||||||
ads = obj.get("ads", [])
|
|
||||||
color_values = set()
|
|
||||||
|
|
||||||
for ad in ads:
|
|
||||||
variants = getattr(ad, "variants", [])
|
|
||||||
for v in variants.all():
|
|
||||||
if v.variant == AdVariantType.COLOR:
|
|
||||||
color_values.add(v.value)
|
|
||||||
|
|
||||||
return list(color_values)
|
|
||||||
|
|
||||||
def get_sizes(self, obj):
|
|
||||||
ads = obj.get("ads", [])
|
|
||||||
size_values = set()
|
|
||||||
|
|
||||||
for ad in ads:
|
|
||||||
variants = getattr(ad, "variants", [])
|
|
||||||
for v in variants.all():
|
|
||||||
if v.variant == AdVariantType.SIZE:
|
|
||||||
size_values.add(v.value)
|
|
||||||
|
|
||||||
return list(size_values)
|
|
||||||
|
|
||||||
def get_min_price(self, obj):
|
|
||||||
ads = obj.get("ads", [])
|
|
||||||
prices = []
|
|
||||||
|
|
||||||
for ad in ads:
|
|
||||||
ad_data = ListAdSerializer(ad, context=self.context).data
|
|
||||||
price = ad_data.get("price")
|
|
||||||
if price is not None:
|
|
||||||
prices.append(price)
|
|
||||||
|
|
||||||
return min(prices) if prices else None
|
|
||||||
|
|
||||||
def get_max_price(self, obj):
|
|
||||||
ads = obj.get("ads", [])
|
|
||||||
prices = []
|
|
||||||
|
|
||||||
for ad in ads:
|
|
||||||
ad_data = ListAdSerializer(ad, context=self.context).data
|
|
||||||
price = ad_data.get("price")
|
|
||||||
if price is not None:
|
|
||||||
prices.append(price)
|
|
||||||
|
|
||||||
return max(prices) if prices else None
|
|
||||||
|
|
||||||
|
|
||||||
class RetrieveAdSerializer(BaseAdSerializer):
|
|
||||||
variants = AdVariantSerializer(many=True, read_only=True)
|
variants = AdVariantSerializer(many=True, read_only=True)
|
||||||
images = serializers.SerializerMethodField()
|
images = AdImageSerializer(many=True, read_only=True)
|
||||||
colors = serializers.SerializerMethodField()
|
colors = serializers.SerializerMethodField()
|
||||||
sizes = serializers.SerializerMethodField()
|
sizes = serializers.SerializerMethodField()
|
||||||
creator = serializers.SerializerMethodField()
|
creator = serializers.SerializerMethodField()
|
||||||
options = AdOptionSerializer(many=True, read_only=True)
|
options = AdOptionSerializer(many=True, read_only=True)
|
||||||
|
category = CategorySerializer(read_only=True)
|
||||||
|
|
||||||
class Meta(BaseAdSerializer.Meta):
|
class Meta(AdListSerializer.Meta):
|
||||||
fields = [
|
fields = AdListSerializer.Meta.fields + [
|
||||||
"id",
|
"description",
|
||||||
"name",
|
|
||||||
"price",
|
|
||||||
"image",
|
|
||||||
"star",
|
|
||||||
"comment_count",
|
|
||||||
"is_liked",
|
|
||||||
"images",
|
"images",
|
||||||
"variants",
|
"variants",
|
||||||
"colors",
|
"colors",
|
||||||
"sizes",
|
"sizes",
|
||||||
"creator",
|
"creator",
|
||||||
"description",
|
"options",
|
||||||
"options"
|
"category",
|
||||||
|
"ad_type",
|
||||||
|
"physical_product",
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_images(self, obj):
|
|
||||||
objects = obj.images.all()
|
|
||||||
return AdImageSerializer(objects, many=True, context=self.context).data
|
|
||||||
|
|
||||||
def get_colors(self, obj):
|
def get_colors(self, obj):
|
||||||
color_values = set()
|
"""Get unique colors from variants"""
|
||||||
|
return list(obj.variants.values_list("color", flat=True).distinct())
|
||||||
variants = getattr(obj, "variants", [])
|
|
||||||
for v in variants.all():
|
|
||||||
if v.variant == AdVariantType.COLOR:
|
|
||||||
color_values.add(v.value)
|
|
||||||
|
|
||||||
return list(color_values)
|
|
||||||
|
|
||||||
def get_sizes(self, obj):
|
def get_sizes(self, obj):
|
||||||
size_values = set()
|
"""Get unique sizes from variants"""
|
||||||
|
return list(obj.variants.values_list("size", flat=True).distinct())
|
||||||
variants = getattr(obj, "variants", [])
|
|
||||||
for v in variants.all():
|
|
||||||
if v.variant == AdVariantType.SIZE:
|
|
||||||
size_values.add(v.value)
|
|
||||||
return list(size_values)
|
|
||||||
|
|
||||||
def get_creator(self, obj):
|
def get_creator(self, obj):
|
||||||
|
"""Get creator information"""
|
||||||
user = obj.user
|
user = obj.user
|
||||||
user_type = user.account_type
|
|
||||||
request = self.context.get("request")
|
request = self.context.get("request")
|
||||||
|
|
||||||
avatar_url = request.build_absolute_uri(user.avatar.url) if user.avatar else None
|
avatar_url = None
|
||||||
|
if user.avatar and request:
|
||||||
|
avatar_url = request.build_absolute_uri(user.avatar.url)
|
||||||
|
|
||||||
if user_type == AccountType.BUSINESS:
|
if user.account_type == AccountType.BUSINESS:
|
||||||
return {
|
username = user.business.name if hasattr(user, "business") else user.username
|
||||||
"username": user.business.name,
|
|
||||||
"avatar": avatar_url,
|
|
||||||
"create_at": user.validated_at,
|
|
||||||
"last_live": "endi qo'shamiz! waiting pls ))"
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
username = f"{user.first_name} {user.last_name}"
|
username = f"{user.first_name} {user.last_name}".strip() or user.username
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
"id": user.id,
|
||||||
"username": username,
|
"username": username,
|
||||||
"avatar": avatar_url,
|
"avatar": avatar_url,
|
||||||
"create_at": user.validated_at,
|
"account_type": user.account_type,
|
||||||
"last_live": "endi qo'shamiz! waiting pls ))"
|
"joined_at": user.date_joined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CreateAdSerializer(BaseAdSerializer):
|
class AdCreateSerializer(serializers.ModelSerializer):
|
||||||
class Meta(BaseAdSerializer.Meta): ...
|
class Meta:
|
||||||
|
model = AdModel
|
||||||
|
fields = [
|
||||||
|
"name",
|
||||||
|
"ad_type",
|
||||||
|
"category",
|
||||||
|
"ad_category_type",
|
||||||
|
"discount",
|
||||||
|
"is_available",
|
||||||
|
"physical_product",
|
||||||
|
"plan",
|
||||||
|
"tags",
|
||||||
|
"image",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
validated_data["user"] = self.context["request"].user
|
||||||
|
return super().create(validated_data)
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.db.models import Avg
|
from django.db.models import Avg
|
||||||
from core.apps.api.models import AdModel, AdVariant
|
from core.apps.api.models import AdModel, AdVariantModel
|
||||||
from core.apps.accounts.models import UserLike
|
from core.apps.accounts.models import UserLike
|
||||||
|
|
||||||
|
|
||||||
class AdVariantSerializer(serializers.ModelSerializer):
|
class AdVariantSerializer(serializers.ModelSerializer):
|
||||||
|
color_name = serializers.CharField(source="color.name", read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdVariant
|
model = AdVariantModel
|
||||||
fields = [
|
fields = ["id", "value", "color_name", "is_available", "price"]
|
||||||
"variant",
|
read_only_fields = fields
|
||||||
"value",
|
|
||||||
"is_available",
|
|
||||||
"price",
|
|
||||||
"discount",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class BaseHomeAdSerializer(serializers.ModelSerializer):
|
class HomeAdListSerializer(serializers.ModelSerializer):
|
||||||
star = serializers.SerializerMethodField()
|
"""Optimized serializer for home page ad listing"""
|
||||||
comment_count = serializers.SerializerMethodField()
|
rating = serializers.SerializerMethodField()
|
||||||
price = serializers.SerializerMethodField()
|
reviews_count = serializers.SerializerMethodField()
|
||||||
discount = serializers.SerializerMethodField()
|
|
||||||
is_liked = serializers.SerializerMethodField()
|
is_liked = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -30,54 +26,25 @@ class BaseHomeAdSerializer(serializers.ModelSerializer):
|
|||||||
"name",
|
"name",
|
||||||
"price",
|
"price",
|
||||||
"image",
|
"image",
|
||||||
"star",
|
"rating",
|
||||||
"comment_count",
|
"reviews_count",
|
||||||
"discount",
|
"discount",
|
||||||
"is_liked",
|
"is_liked",
|
||||||
]
|
]
|
||||||
|
read_only_fields = fields
|
||||||
|
|
||||||
def _get_first_variant(self, obj):
|
def get_rating(self, obj):
|
||||||
if not hasattr(self, "_variant_cache"):
|
"""Get average rating"""
|
||||||
self._variant_cache = {}
|
avg = obj.feedbacks.aggregate(avg=Avg("star"))["avg"]
|
||||||
if obj.id not in self._variant_cache:
|
return round(avg, 1) if avg else 0
|
||||||
self._variant_cache[obj.id] = obj.variants.order_by("price").first()
|
|
||||||
return self._variant_cache[obj.id]
|
|
||||||
|
|
||||||
def get_price(self, obj):
|
def get_reviews_count(self, obj):
|
||||||
variant = self._get_first_variant(obj)
|
"""Get feedback count"""
|
||||||
if not variant:
|
return obj.feedbacks.count()
|
||||||
return obj.price
|
|
||||||
return variant.price if variant else 0
|
|
||||||
|
|
||||||
def get_discount(self, obj):
|
|
||||||
variant = self._get_first_variant(obj)
|
|
||||||
return variant.discount if variant else -1.0
|
|
||||||
|
|
||||||
def get_star(self, obj):
|
|
||||||
avg = obj.feedback.aggregate(avg=Avg("star"))["avg"]
|
|
||||||
return avg or 0
|
|
||||||
|
|
||||||
def get_comment_count(self, obj):
|
|
||||||
count = obj.feedback.count()
|
|
||||||
return count or 0
|
|
||||||
|
|
||||||
def get_is_liked(self, obj):
|
def get_is_liked(self, obj):
|
||||||
|
"""Check if user liked this ad"""
|
||||||
request = self.context.get("request")
|
request = self.context.get("request")
|
||||||
user = getattr(request, "user", None)
|
if not request or not request.user.is_authenticated:
|
||||||
|
|
||||||
if not user or not user.is_authenticated:
|
|
||||||
return False
|
return False
|
||||||
|
return UserLike.objects.filter(user=request.user, ad=obj).exists()
|
||||||
return UserLike.objects.filter(user=user, ad=obj).exists()
|
|
||||||
|
|
||||||
|
|
||||||
class ListHomeAdSerializer(BaseHomeAdSerializer):
|
|
||||||
class Meta(BaseHomeAdSerializer.Meta): ...
|
|
||||||
|
|
||||||
|
|
||||||
class RetrieveHomeAdSerializer(BaseHomeAdSerializer):
|
|
||||||
class Meta(BaseHomeAdSerializer.Meta): ...
|
|
||||||
|
|
||||||
|
|
||||||
class CreateHomeAdSerializer(BaseHomeAdSerializer):
|
|
||||||
class Meta(BaseHomeAdSerializer.Meta): ...
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from .banner import * # noqa
|
from .banner import *
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from core.apps.api.models import Banner
|
from core.apps.api.models import BannerModel
|
||||||
|
|
||||||
|
|
||||||
class BaseBannerSerializer(serializers.ModelSerializer):
|
class BaseBannerSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Banner
|
model = BannerModel
|
||||||
fields = [
|
fields = [
|
||||||
|
"id",
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
"mobile_image",
|
"mobile_image",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from .category import * # noqa
|
from .category import *
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from core.apps.api.models import Category
|
from core.apps.api.models import CategoryModel
|
||||||
|
|
||||||
|
|
||||||
class BaseCategorySerializer(serializers.ModelSerializer):
|
class BaseCategorySerializer(serializers.ModelSerializer):
|
||||||
children = serializers.SerializerMethodField()
|
children = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Category
|
model = CategoryModel
|
||||||
fields = [
|
fields = [
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
|
|||||||
2
core/apps/api/serializers/common/__init__.py
Normal file
2
core/apps/api/serializers/common/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .color import * # noqa
|
||||||
|
from .size import * # noqa
|
||||||
29
core/apps/api/serializers/common/color.py
Normal file
29
core/apps/api/serializers/common/color.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from core.apps.api.models import ColorModel
|
||||||
|
|
||||||
|
|
||||||
|
class BaseColorSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ColorModel
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"color",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ListColorSerializer(BaseColorSerializer):
|
||||||
|
class Meta(BaseColorSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveColorSerializer(BaseColorSerializer):
|
||||||
|
class Meta(BaseColorSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class CreateColorSerializer(BaseColorSerializer):
|
||||||
|
class Meta(BaseColorSerializer.Meta):
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
]
|
||||||
28
core/apps/api/serializers/common/size.py
Normal file
28
core/apps/api/serializers/common/size.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from core.apps.api.models import SizeModel
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSizeSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SizeModel
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ListSizeSerializer(BaseSizeSerializer):
|
||||||
|
class Meta(BaseSizeSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveSizeSerializer(BaseSizeSerializer):
|
||||||
|
class Meta(BaseSizeSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSizeSerializer(BaseSizeSerializer):
|
||||||
|
class Meta(BaseSizeSerializer.Meta):
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
]
|
||||||
1
core/apps/api/serializers/feedback/__init__.py
Normal file
1
core/apps/api/serializers/feedback/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .feedback import *
|
||||||
84
core/apps/api/serializers/feedback/feedback.py
Normal file
84
core/apps/api/serializers/feedback/feedback.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from django.db.models import Avg
|
||||||
|
from core.apps.api.models import FeedbackModel, FeedbackImageModel, AdModel
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackImageSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = FeedbackImageModel
|
||||||
|
fields = ["id", "image"]
|
||||||
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackListSerializer(serializers.ModelSerializer):
|
||||||
|
"""List serializer for feedback with user and ad info"""
|
||||||
|
user_name = serializers.CharField(source="user.username", read_only=True)
|
||||||
|
ad_name = serializers.CharField(source="ad.name", read_only=True)
|
||||||
|
images = FeedbackImageSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FeedbackModel
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"star",
|
||||||
|
"user",
|
||||||
|
"user_name",
|
||||||
|
"ad",
|
||||||
|
"ad_name",
|
||||||
|
"comment",
|
||||||
|
"images",
|
||||||
|
"created_at"
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "user", "user_name", "ad_name", "created_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackDetailSerializer(FeedbackListSerializer):
|
||||||
|
"""Detailed feedback serializer"""
|
||||||
|
class Meta(FeedbackListSerializer.Meta):
|
||||||
|
fields = FeedbackListSerializer.Meta.fields + ["updated_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackCreateSerializer(serializers.ModelSerializer):
|
||||||
|
"""Create feedback serializer"""
|
||||||
|
images = serializers.ListField(
|
||||||
|
child=serializers.ImageField(),
|
||||||
|
write_only=True,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FeedbackModel
|
||||||
|
fields = ["ad", "star", "comment", "images"]
|
||||||
|
|
||||||
|
def validate_star(self, value):
|
||||||
|
"""Validate star rating is between 1 and 5"""
|
||||||
|
if not 1 <= value <= 5:
|
||||||
|
raise serializers.ValidationError("Rating must be between 1 and 5")
|
||||||
|
return value
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
images_data = validated_data.pop("images", [])
|
||||||
|
validated_data["user"] = self.context["request"].user
|
||||||
|
|
||||||
|
feedback = FeedbackModel.objects.create(**validated_data)
|
||||||
|
|
||||||
|
# Create feedback images
|
||||||
|
for image_data in images_data:
|
||||||
|
FeedbackImageModel.objects.create(
|
||||||
|
feedback=feedback,
|
||||||
|
image=image_data
|
||||||
|
)
|
||||||
|
|
||||||
|
return feedback
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackUpdateSerializer(serializers.ModelSerializer):
|
||||||
|
"""Update feedback serializer"""
|
||||||
|
class Meta:
|
||||||
|
model = FeedbackModel
|
||||||
|
fields = ["star", "comment"]
|
||||||
|
|
||||||
|
def validate_star(self, value):
|
||||||
|
if not 1 <= value <= 5:
|
||||||
|
raise serializers.ValidationError("Rating must be between 1 and 5")
|
||||||
|
return value
|
||||||
@@ -1 +1 @@
|
|||||||
from .natification import * # noqa
|
from .notification import *
|
||||||
|
|||||||
1
core/apps/api/serializers/order/__init__.py
Normal file
1
core/apps/api/serializers/order/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .order import *
|
||||||
149
core/apps/api/serializers/order/order.py
Normal file
149
core/apps/api/serializers/order/order.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from core.apps.api.models import OrderModel, OrderItemModel, AdModel
|
||||||
|
from core.apps.accounts.models import Address
|
||||||
|
|
||||||
|
|
||||||
|
class OrderItemSerializer(serializers.ModelSerializer):
|
||||||
|
"""Order item serializer"""
|
||||||
|
ad_name = serializers.CharField(source="ad.name", read_only=True)
|
||||||
|
ad_image = serializers.ImageField(source="ad.image", read_only=True)
|
||||||
|
subtotal = serializers.DecimalField(
|
||||||
|
max_digits=10,
|
||||||
|
decimal_places=2,
|
||||||
|
read_only=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OrderItemModel
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"ad",
|
||||||
|
"ad_name",
|
||||||
|
"ad_image",
|
||||||
|
"price",
|
||||||
|
"quantity",
|
||||||
|
"subtotal"
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "ad_name", "ad_image", "subtotal"]
|
||||||
|
|
||||||
|
|
||||||
|
class OrderListSerializer(serializers.ModelSerializer):
|
||||||
|
"""List serializer for orders"""
|
||||||
|
items_count = serializers.SerializerMethodField()
|
||||||
|
status_display = serializers.CharField(source="get_status_display", read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OrderModel
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"status",
|
||||||
|
"status_display",
|
||||||
|
"total_amount",
|
||||||
|
"items_count",
|
||||||
|
"created_at"
|
||||||
|
]
|
||||||
|
read_only_fields = fields
|
||||||
|
|
||||||
|
def get_items_count(self, obj):
|
||||||
|
return obj.items.count()
|
||||||
|
|
||||||
|
|
||||||
|
class OrderDetailSerializer(serializers.ModelSerializer):
|
||||||
|
"""Detailed order serializer"""
|
||||||
|
items = OrderItemSerializer(many=True, read_only=True)
|
||||||
|
user_name = serializers.CharField(source="user.username", read_only=True)
|
||||||
|
address_details = serializers.SerializerMethodField()
|
||||||
|
status_display = serializers.CharField(source="get_status_display", read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OrderModel
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"user",
|
||||||
|
"user_name",
|
||||||
|
"status",
|
||||||
|
"status_display",
|
||||||
|
"address",
|
||||||
|
"address_details",
|
||||||
|
"total_amount",
|
||||||
|
"items",
|
||||||
|
"created_at",
|
||||||
|
"updated_at"
|
||||||
|
]
|
||||||
|
read_only_fields = [
|
||||||
|
"id", "user", "user_name", "status_display",
|
||||||
|
"total_amount", "created_at", "updated_at"
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_address_details(self, obj):
|
||||||
|
if obj.address:
|
||||||
|
return {
|
||||||
|
"id": obj.address.id,
|
||||||
|
"street": obj.address.street,
|
||||||
|
"city": obj.address.city,
|
||||||
|
"country": obj.address.country,
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class OrderItemCreateSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for creating order items"""
|
||||||
|
ad = serializers.PrimaryKeyRelatedField(queryset=AdModel.objects.all())
|
||||||
|
quantity = serializers.IntegerField(min_value=1, default=1)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderCreateSerializer(serializers.ModelSerializer):
|
||||||
|
"""Create order serializer"""
|
||||||
|
items = OrderItemCreateSerializer(many=True, write_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OrderModel
|
||||||
|
fields = ["address", "items"]
|
||||||
|
|
||||||
|
def validate_items(self, value):
|
||||||
|
if not value:
|
||||||
|
raise serializers.ValidationError("Order must have at least one item")
|
||||||
|
return value
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
items_data = validated_data.pop("items")
|
||||||
|
validated_data["user"] = self.context["request"].user
|
||||||
|
|
||||||
|
# Create order
|
||||||
|
order = OrderModel.objects.create(**validated_data)
|
||||||
|
|
||||||
|
# Create order items and calculate total
|
||||||
|
total = 0
|
||||||
|
for item_data in items_data:
|
||||||
|
ad = item_data["ad"]
|
||||||
|
quantity = item_data.get("quantity", 1)
|
||||||
|
price = ad.price
|
||||||
|
|
||||||
|
OrderItemModel.objects.create(
|
||||||
|
order=order,
|
||||||
|
ad=ad,
|
||||||
|
price=price,
|
||||||
|
quantity=quantity
|
||||||
|
)
|
||||||
|
total += price * quantity
|
||||||
|
|
||||||
|
# Update order total
|
||||||
|
order.total_amount = total
|
||||||
|
order.save()
|
||||||
|
|
||||||
|
return order
|
||||||
|
|
||||||
|
|
||||||
|
class OrderUpdateSerializer(serializers.ModelSerializer):
|
||||||
|
"""Update order serializer"""
|
||||||
|
class Meta:
|
||||||
|
model = OrderModel
|
||||||
|
fields = ["status", "address"]
|
||||||
|
|
||||||
|
def validate_status(self, value):
|
||||||
|
# Add business logic for status transitions
|
||||||
|
current_status = self.instance.status
|
||||||
|
# Example: Can't change completed orders
|
||||||
|
if current_status == "completed":
|
||||||
|
raise serializers.ValidationError("Cannot modify completed orders")
|
||||||
|
return value
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
from .search import * # noqa
|
from .history import *
|
||||||
from .search_ads import * # noqa
|
from .ad import *
|
||||||
|
|||||||
1
core/apps/api/serializers/tags/__init__.py
Normal file
1
core/apps/api/serializers/tags/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .tags import *
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user