This commit is contained in:
A'zamov Samandar
2025-12-06 21:50:28 +05:00
parent 3aa20fdaa1
commit f5766aa319
140 changed files with 2376 additions and 1582 deletions

View File

@@ -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.utils.timezone
@@ -15,6 +15,51 @@ class Migration(migrations.Migration):
]
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(
name='User',
fields=[
@@ -33,7 +78,10 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=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)),
('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')),
('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,
},
),
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(
name='ResetToken',
fields=[
@@ -57,4 +144,19 @@ class Migration(migrations.Migration):
'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',
},
),
]

View 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'),
),
]

View File

@@ -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),
),
]

View File

@@ -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',
},
),
]

View File

@@ -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',
},
),
]

View File

@@ -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',
},
),
]

View File

@@ -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',
),
]

View File

@@ -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',
},
),
]

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -1,5 +1,5 @@
from .category import * # noqa
from .ad import * # noqa
from .ad_items import * # noqa
from .feedback import * # noqa
from .banner import * # noqa
from .ad import *
from .banner import *
from .category import *
from .common import * # noqa
from .feedback import *

View File

@@ -1 +1,4 @@
from .ad import * # noqa
from .ad import *
from .image import *
from .option import *
from .variant import *

View File

@@ -1,21 +1,24 @@
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):
model = AdImage
class AdImageInline(TabularInline):
model = AdImageModel
extra = 1
tab = True
class AdVariantInline(admin.TabularInline):
model = AdVariant
class AdVariantInline(TabularInline):
model = AdVariantModel
extra = 1
tab = True
@admin.register(AdModel)
class AdModelAdmin(ModelAdmin):
autocomplete_fields = ["tags"]
list_display = (
"id",
"__str__",

View File

@@ -1,10 +1,10 @@
from django.contrib import admin
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):
list_display = (
"id",

View File

@@ -1,10 +1,10 @@
from django.contrib import admin
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):
list_display = (
"id",

View File

@@ -1,10 +1,10 @@
from django.contrib import admin
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):
list_display = (
"id",

View File

@@ -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

View File

@@ -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__",
)

View File

@@ -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__",
)

View File

@@ -1 +1 @@
from .banner import * # noqa
from .banner import *

View File

@@ -1,10 +1,10 @@
from django.contrib import admin
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):
list_display = (
"id",

View File

@@ -1 +1 @@
from .category import * # noqa
from .category import *

View File

@@ -1,10 +1,10 @@
from django.contrib import admin
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):
list_display = (
"id",

View 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__",
)

View File

@@ -1 +1 @@
from .feedback import * # noqa
from .feedback import *

View File

@@ -1,10 +1,10 @@
from django.contrib import admin
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):
list_display = (
"id",
@@ -12,7 +12,7 @@ class FeedbackAdmin(ModelAdmin):
)
@admin.register(FeedbackImages)
@admin.register(FeedbackImageModel)
class FeedbackImagesAdmin(ModelAdmin):
list_display = (
"id",

View File

@@ -1,3 +1,3 @@
from .ad_type import * # noqa
from .ad_variant_type import * # noqa
from .order_status import * # noqa
from .ad_type import *
from .ad_variant_type import *
from .order_status import *

View File

@@ -1,2 +1,2 @@
from .category import * # noqa
from .ad import * # noqa
from .category import *
from .ad import *

View File

@@ -1,8 +1,6 @@
from django_filters import rest_framework as filters
from django.db.models import (
F, Case, When, FloatField, ExpressionWrapper, Subquery, OuterRef
)
from core.apps.api.models import AdVariant, AdModel
from django.db.models import F, Case, When, FloatField, ExpressionWrapper, Subquery, OuterRef
from core.apps.api.models import AdVariantModel, AdModel
class AdFilter(filters.FilterSet):
@@ -16,70 +14,28 @@ class AdFilter(filters.FilterSet):
has_discount = filters.BooleanFilter(method="filter_has_discount")
has_normal_user = filters.BooleanFilter(method="filter_has_normal_user")
has_business_user = filters.BooleanFilter(method="filter_has_business_user")
discount = filters.BooleanFilter(method="filter_discount")
class Meta:
model = AdModel
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):
return queryset.filter(
user__account_type="business"
)
return queryset.filter(user__account_type="business")
def filter_has_normal_user(self, queryset, name, value):
return queryset.filter(
user__account_type="personal"
)
return queryset.filter(user__account_type="personal")
def filter_has_discount(self, queryset, name, value):
return queryset.filter(
variants__discount__gte=1
).distinct()
return queryset.filter(variants__discount__gte=1).distinct()
def filter_by_size(self, queryset, name, value):
return queryset.filter(
variants__variant="Size",
variants__value__iexact=value
).distinct()
return queryset.filter(variants__variant="Size", variants__value__iexact=value).distinct()
def filter_by_color(self, queryset, name, value):
return queryset.filter(
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)
return queryset.filter(variants__variant="Color", variants__value__iexact=value).distinct()

View File

@@ -1,13 +1,13 @@
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):
# name = filters.CharFilter(field_name="name", lookup_expr="icontains")
class Meta:
model = Category
model = CategoryModel
fields = [
"show_home",
"id",

View File

View 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

View File

@@ -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
@@ -8,27 +11,330 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('accounts', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
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=[
('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')),
('mobile_image', models.ImageField(upload_to='banner/mobile_image/', verbose_name='Mobile Image')),
('desktop_image', models.ImageField(upload_to='banner/desktop_image/', verbose_name='Desktop Image')),
('mobile_image', models.ImageField(upload_to='banners/mobile/', verbose_name='Mobile Image')),
('desktop_image', models.ImageField(upload_to='banners/desktop/', verbose_name='Desktop Image')),
('link', models.URLField(verbose_name='Link')),
('bg_color', models.CharField(max_length=255, verbose_name='BG Color')),
('text_color', models.CharField(max_length=255, verbose_name='Text Color')),
('bg_color', models.CharField(default='#FFFFFF', max_length=7, verbose_name='Background 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={
'verbose_name': 'Banner',
'verbose_name_plural': 'Banners',
'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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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',
),
]

View File

@@ -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'),
),
]

View 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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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,
),
]

View File

@@ -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'),
),
]

View File

@@ -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',
),
]

View File

@@ -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'),
),
]

View File

@@ -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,
),
]

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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 = [
]

View File

@@ -1,5 +1,5 @@
from .banner import * # noqa
from .feedback import * # noqa
from .ad import * # noqa
from .ad_items import * # noqa
from .order import * # noqa
from .banner import *
from .feedback import *
from .ad import *
from .common import *
from .order import *

View File

@@ -1,2 +1,6 @@
from .ad import * # noqa
from .category import * # noqa
from .ad import *
from .category import *
from .variant import *
from .image import *
from .option import *
from .size import *

View File

@@ -1,45 +1,43 @@
# type: ignore
from django.db import models
from django_core.models.base import AbstractBaseModel
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model
from core.apps.api.choices.ad_type import AdType, AdCategoryType
from model_bakery import baker
class AdModel(AbstractBaseModel):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"), related_name="ad")
name = models.CharField(verbose_name=_("Name"), max_length=255)
ad_type = models.CharField(verbose_name=_("Type"), max_length=255, choices=AdType)
category = models.ForeignKey("api.Category", on_delete=models.CASCADE, verbose_name=_("Category"))
ad_category_type = models.CharField(verbose_name=_("Type"), max_length=255, choices=AdCategoryType)
price = models.DecimalField(verbose_name=_("Price"), max_digits=10, decimal_places=2, null=True, blank=True)
is_available = models.BooleanField(verbose_name=_("Is available"), default=True, blank=True, null=True)
physical_product = models.BooleanField(verbose_name=_("Physical product"), default=False)
plan = models.ForeignKey("api.AdTopPlan", on_delete=models.CASCADE, verbose_name=_("Plan"))
tags = models.ManyToManyField("api.Tags", verbose_name=_("Tags"))
image = models.ImageField(verbose_name=_("Image"))
description = models.TextField(verbose_name=_("Description"))
@classmethod
def _baker(cls):
return baker.make(cls)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"), related_name="ads")
name = models.CharField(_("Name"), max_length=255)
ad_type = models.CharField(_("Type"), max_length=255, choices=AdType)
category = models.ForeignKey("api.CategoryModel", on_delete=models.CASCADE, verbose_name=_("Category"))
ad_category_type = models.CharField(_("Category Type"), max_length=255, choices=AdCategoryType)
_price = models.DecimalField(_("Price"), max_digits=10, decimal_places=2, null=True, blank=True, db_column="price")
discount = models.DecimalField(_("Discount"), max_digits=10, decimal_places=2, default=-1)
is_available = models.BooleanField(_("Is available"), default=True)
physical_product = models.BooleanField(_("Physical product"), default=False)
plan = models.ForeignKey("api.AdTopPlanModel", on_delete=models.CASCADE, verbose_name=_("Plan"))
tags = models.ManyToManyField("api.TagsModel", verbose_name=_("Tags"), blank=True)
image = models.ImageField(_("Image"), upload_to="ads/")
description = models.TextField(_("Description"))
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:
db_table = "ad"
verbose_name = _("Ad")
verbose_name_plural = _("Ads")
class Color(AbstractBaseModel):
name = models.CharField(verbose_name=_("Name"), max_length=255)
def __str__(self):
return str(self.pk)
class Meta:
db_table = "color"
verbose_name = _("Color")
verbose_name_plural = _("Colors")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["-created_at"]),
models.Index(fields=["category", "is_available"]),
]

View File

@@ -2,26 +2,45 @@ from django.db import models
from django_core.models.base import AbstractBaseModel
from django.utils.translation import gettext_lazy as _
from core.apps.api.choices import AdCategoryType
from model_bakery import baker
class Category(AbstractBaseModel):
name = models.CharField(max_length=255, verbose_name=_('Category Name'))
parent = models.ForeignKey('self', null=True, blank=True, related_name='children', on_delete=models.CASCADE)
show_home = models.BooleanField(default=False, verbose_name=_('Show Home'))
level = models.IntegerField(default=0, verbose_name=_('Level'))
image = models.ImageField(verbose_name=_('Image'), null=True, blank=True)
category_type = models.CharField(max_length=255, verbose_name=_('Category Type'), choices=AdCategoryType,
default=AdCategoryType.PRODUCT)
@classmethod
def _baker(cls):
return baker.make(cls)
class CategoryModel(AbstractBaseModel):
name = models.CharField(_("Category Name"), max_length=255)
parent = models.ForeignKey(
"self",
null=True,
blank=True,
related_name="children",
on_delete=models.CASCADE,
verbose_name=_("Parent Category")
)
show_home = models.BooleanField(_("Show on Home"), default=False)
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):
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:
db_table = 'category'
verbose_name = _('Category')
verbose_name_plural = _('Categories')
db_table = "category"
verbose_name = _("Category")
verbose_name_plural = _("Categories")
ordering = ["level", "name"]
indexes = [
models.Index(fields=["parent", "show_home"]),
models.Index(fields=["level"]),
]

View 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"]),
]

View 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"]]

View 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")

View 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"]),
]

View File

@@ -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

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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')

View File

@@ -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")

View File

@@ -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")

View File

@@ -1 +1 @@
from .banner import * # noqa
from .banner import *

View File

@@ -1,26 +1,30 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_core.models.base import AbstractBaseModel
from model_bakery import baker
class Banner(AbstractBaseModel):
title = models.CharField(max_length=255, verbose_name=_("Title"))
description = models.TextField(verbose_name=_("Description"))
mobile_image = models.ImageField(verbose_name=_("Mobile Image"), upload_to="banner/mobile_image/")
desktop_image = models.ImageField(verbose_name=_("Desktop Image"), upload_to="banner/desktop_image/")
link = models.URLField(verbose_name=_("Link"))
bg_color = models.CharField(verbose_name=_("BG Color"), max_length=255)
text_color = models.CharField(verbose_name=_("Text Color"), max_length=255)
@classmethod
def _baker(cls):
return baker.make(cls)
class BannerModel(AbstractBaseModel):
title = models.CharField(_("Title"), max_length=255)
description = models.TextField(_("Description"))
mobile_image = models.ImageField(
_("Mobile Image"),
upload_to="banners/mobile/"
)
desktop_image = models.ImageField(
_("Desktop Image"),
upload_to="banners/desktop/"
)
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):
return str(self.pk)
return self.title
class Meta:
db_table = "banner"
verbose_name = _("Banner")
verbose_name_plural = _("Banners")
ordering = ["order", "-created_at"]

View File

@@ -0,0 +1,4 @@
from .tags import *
from .plan import *
from .size import * # noqa
from .color import * # noqa

View 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"]

View 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"]

View 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"]

View 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"]

View File

@@ -1 +1 @@
from .feedback import * # noqa
from .feedback import *

View File

@@ -2,33 +2,57 @@ from django.db import models
from django_core.models.base import AbstractBaseModel
from django.utils.translation import gettext_lazy as _
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):
star = models.IntegerField(default=0, verbose_name=_("Star"))
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"))
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad"), related_name="feedback")
comment = models.CharField(max_length=255, verbose_name=_("Comment"))
class FeedbackModel(AbstractBaseModel):
star = models.IntegerField(
_("Rating"),
default=5,
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):
return str(self.pk)
return f"{self.user.username} - {self.ad.name} ({self.star}★)"
class Meta:
db_table = "feedback"
verbose_name = _("Feedback")
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):
feedback = models.ForeignKey(Feedback, on_delete=models.CASCADE, verbose_name=_("Feedback"))
image = models.ImageField(verbose_name=_("Image"), upload_to="feedback/"
"images/")
class FeedbackImageModel(AbstractBaseModel):
feedback = models.ForeignKey(
FeedbackModel,
on_delete=models.CASCADE,
verbose_name=_("Feedback"),
related_name="images"
)
image = models.ImageField(_("Image"), upload_to="feedbacks/")
def __str__(self):
return str(self.pk)
return f"Image for {self.feedback}"
class Meta:
db_table = "feedback_images"
verbose_name = _("Feedback Images")
verbose_name = _("Feedback Image")
verbose_name_plural = _("Feedback Images")

View File

@@ -1 +1 @@
from .order import * # noqa
from .order import *

View File

@@ -4,33 +4,82 @@ from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model
from core.apps.api.choices import OrderStatus
from core.apps.accounts.models import Address
from core.apps.api.models import AdModel
class Order(AbstractBaseModel):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"))
status = models.CharField(max_length=255, choices=OrderStatus)
address = models.ForeignKey(Address, on_delete=models.CASCADE, verbose_name=_("Address"))
class OrderModel(AbstractBaseModel):
user = models.ForeignKey(
get_user_model(),
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):
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:
db_table = "order"
verbose_name = _("Order")
verbose_name_plural = _("Orders")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["user", "-created_at"]),
models.Index(fields=["status"]),
]
class OrderItem(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE, verbose_name=_("Order"))
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("Price"))
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad"))
count = models.PositiveIntegerField(default=0, verbose_name=_("Count"))
class OrderItemModel(AbstractBaseModel):
order = models.ForeignKey(
OrderModel,
on_delete=models.CASCADE,
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):
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:
db_table = "order_item"
verbose_name = _("Order Item")
verbose_name_plural = _("Order Items")
unique_together = [["order", "ad"]]

View File

@@ -1,6 +1,7 @@
from .category import * # noqa
from .search import * # noqa
from .ad import * # noqa
from .user import * # noqa
from .notification import * # noqa
from .banner import * # noqa
from .ad import *
from .banner import *
from .category import *
from .common import * # noqa
from .notification import *
from .search import *
from .user import *

View File

@@ -1,2 +1,2 @@
from .home_api import * # noqa
from .ad import * # noqa
from .home_api import *
from .ad import *

View File

@@ -1,270 +1,148 @@
from rest_framework import serializers
from django.db.models import Avg
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.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 Meta:
model = AdOption
fields = [
"id",
"name",
"value",
]
model = AdOptionModel
fields = ["id", "name", "value"]
read_only_fields = ["id"]
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
model = CategoryModel
fields = ["id", "name"]
read_only_fields = ["id"]
class AdImageSerializer(serializers.ModelSerializer):
class Meta:
model = AdImage
fields = [
"image",
"ad_variant"
]
model = AdImageModel
fields = ["id", "image", "ad_variant", "is_primary", "order"]
read_only_fields = ["id"]
def to_representation(self, instance):
data = super().to_representation(instance)
if instance.ad_variant is None:
if not instance.ad_variant:
data.pop("ad_variant", None)
return data
class AdVariantSerializer(serializers.ModelSerializer):
color = ListColorSerializer()
size = ListSizeSerializer()
class Meta:
model = AdVariant
fields = [
"id",
"variant",
"value",
"is_available",
"price",
"discount",
]
model = AdVariantModel
fields = ["id", "size", "color", "is_available", "price", "stock_quantity"]
read_only_fields = ["id", "color_name"]
class BaseAdSerializer(serializers.ModelSerializer):
class AdListSerializer(serializers.ModelSerializer):
is_liked = serializers.SerializerMethodField()
star = serializers.SerializerMethodField()
comment_count = serializers.SerializerMethodField()
rating = serializers.SerializerMethodField()
reviews_count = serializers.SerializerMethodField()
class Meta:
model = AdModel
fields = [
"id",
"name",
"price",
"image",
"is_liked",
"star",
"comment_count",
]
fields = ["id", "name", "price", "image", "discount", "is_liked", "rating", "reviews_count", "is_available"]
read_only_fields = fields
def get_star(self, obj):
avg = obj.feedback.aggregate(avg=Avg("star"))["avg"]
return avg or 0
def get_rating(self, obj):
"""Get average rating from feedbacks"""
avg = obj.feedbacks.aggregate(avg=Avg("star"))["avg"]
return round(avg, 1) if avg else 0
def get_comment_count(self, obj):
count = obj.feedback.count()
return count or 0
def get_reviews_count(self, obj):
"""Get total count of feedbacks"""
return obj.feedbacks.count()
def get_is_liked(self, obj):
"""Check if current user liked this ad"""
request = self.context.get("request")
user = getattr(request, "user", None)
if not user or not user.is_authenticated:
if not request or not request.user.is_authenticated:
return False
return UserLike.objects.filter(user=user, ad=obj).exists()
return UserLike.objects.filter(user=request.user, ad=obj).exists()
class ListAdSerializer(BaseAdSerializer):
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):
class AdDetailSerializer(AdListSerializer):
variants = AdVariantSerializer(many=True, read_only=True)
images = serializers.SerializerMethodField()
images = AdImageSerializer(many=True, read_only=True)
colors = serializers.SerializerMethodField()
sizes = serializers.SerializerMethodField()
creator = serializers.SerializerMethodField()
options = AdOptionSerializer(many=True, read_only=True)
category = CategorySerializer(read_only=True)
class Meta(BaseAdSerializer.Meta):
fields = [
"id",
"name",
"price",
"image",
"star",
"comment_count",
"is_liked",
class Meta(AdListSerializer.Meta):
fields = AdListSerializer.Meta.fields + [
"description",
"images",
"variants",
"colors",
"sizes",
"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):
color_values = set()
variants = getattr(obj, "variants", [])
for v in variants.all():
if v.variant == AdVariantType.COLOR:
color_values.add(v.value)
return list(color_values)
"""Get unique colors from variants"""
return list(obj.variants.values_list("color", flat=True).distinct())
def get_sizes(self, obj):
size_values = set()
variants = getattr(obj, "variants", [])
for v in variants.all():
if v.variant == AdVariantType.SIZE:
size_values.add(v.value)
return list(size_values)
"""Get unique sizes from variants"""
return list(obj.variants.values_list("size", flat=True).distinct())
def get_creator(self, obj):
"""Get creator information"""
user = obj.user
user_type = user.account_type
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:
return {
"username": user.business.name,
"avatar": avatar_url,
"create_at": user.validated_at,
"last_live": "endi qo'shamiz! waiting pls ))"
}
if user.account_type == AccountType.BUSINESS:
username = user.business.name if hasattr(user, "business") else user.username
else:
username = f"{user.first_name} {user.last_name}"
return {
"username": username,
"avatar": avatar_url,
"create_at": user.validated_at,
"last_live": "endi qo'shamiz! waiting pls ))"
}
username = f"{user.first_name} {user.last_name}".strip() or user.username
return {
"id": user.id,
"username": username,
"avatar": avatar_url,
"account_type": user.account_type,
"joined_at": user.date_joined,
}
class CreateAdSerializer(BaseAdSerializer):
class Meta(BaseAdSerializer.Meta): ...
class AdCreateSerializer(serializers.ModelSerializer):
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)

View File

@@ -1,26 +1,22 @@
from rest_framework import serializers
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
class AdVariantSerializer(serializers.ModelSerializer):
color_name = serializers.CharField(source="color.name", read_only=True)
class Meta:
model = AdVariant
fields = [
"variant",
"value",
"is_available",
"price",
"discount",
]
model = AdVariantModel
fields = ["id", "value", "color_name", "is_available", "price"]
read_only_fields = fields
class BaseHomeAdSerializer(serializers.ModelSerializer):
star = serializers.SerializerMethodField()
comment_count = serializers.SerializerMethodField()
price = serializers.SerializerMethodField()
discount = serializers.SerializerMethodField()
class HomeAdListSerializer(serializers.ModelSerializer):
"""Optimized serializer for home page ad listing"""
rating = serializers.SerializerMethodField()
reviews_count = serializers.SerializerMethodField()
is_liked = serializers.SerializerMethodField()
class Meta:
@@ -30,54 +26,25 @@ class BaseHomeAdSerializer(serializers.ModelSerializer):
"name",
"price",
"image",
"star",
"comment_count",
"rating",
"reviews_count",
"discount",
"is_liked",
]
read_only_fields = fields
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_rating(self, obj):
"""Get average rating"""
avg = obj.feedbacks.aggregate(avg=Avg("star"))["avg"]
return round(avg, 1) if avg else 0
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
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_reviews_count(self, obj):
"""Get feedback count"""
return obj.feedbacks.count()
def get_is_liked(self, obj):
"""Check if user liked this ad"""
request = self.context.get("request")
user = getattr(request, "user", None)
if not user or not user.is_authenticated:
if not request or not request.user.is_authenticated:
return False
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): ...
return UserLike.objects.filter(user=request.user, ad=obj).exists()

View File

@@ -1 +1 @@
from .banner import * # noqa
from .banner import *

View File

@@ -1,11 +1,12 @@
from rest_framework import serializers
from core.apps.api.models import Banner
from core.apps.api.models import BannerModel
class BaseBannerSerializer(serializers.ModelSerializer):
class Meta:
model = Banner
model = BannerModel
fields = [
"id",
"title",
"description",
"mobile_image",

View File

@@ -1 +1 @@
from .category import * # noqa
from .category import *

View File

@@ -1,13 +1,13 @@
from rest_framework import serializers
from core.apps.api.models import Category
from core.apps.api.models import CategoryModel
class BaseCategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = Category
model = CategoryModel
fields = [
"id",
"name",

View File

@@ -0,0 +1,2 @@
from .color import * # noqa
from .size import * # noqa

View 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",
]

View 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",
]

View File

@@ -0,0 +1 @@
from .feedback import *

View 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

View File

@@ -1 +1 @@
from .natification import * # noqa
from .notification import *

View File

@@ -0,0 +1 @@
from .order import *

View 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

View File

@@ -1,2 +1,2 @@
from .search import * # noqa
from .search_ads import * # noqa
from .history import *
from .ad import *

View File

@@ -0,0 +1 @@
from .tags import *

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