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.db.models.deletion
import django.utils.timezone import django.utils.timezone
@@ -15,6 +15,51 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Notification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=255, verbose_name='Title')),
('description', models.TextField(verbose_name='Description')),
('notification_type', models.CharField(choices=[('System', 'System'), ('Another', 'Another')], max_length=255, verbose_name='Type')),
('long', models.FloatField(verbose_name='Long')),
('lat', models.FloatField(verbose_name='Lat')),
],
options={
'verbose_name': 'Notification',
'verbose_name_plural': 'Notifications',
'db_table': 'notification',
},
),
migrations.CreateModel(
name='UserLike',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'User Like',
'verbose_name_plural': 'User Likes',
'db_table': 'user_like',
},
),
migrations.CreateModel(
name='UserNotification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('is_read', models.BooleanField(default=False, verbose_name='Read')),
],
options={
'verbose_name': 'User Notification',
'verbose_name_plural': 'User Notifications',
'db_table': 'user_notification',
},
),
migrations.CreateModel( migrations.CreateModel(
name='User', name='User',
fields=[ fields=[
@@ -33,7 +78,10 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now_add=True)), ('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)), ('updated_at', models.DateTimeField(auto_now=True)),
('validated_at', models.DateTimeField(blank=True, null=True)), ('validated_at', models.DateTimeField(blank=True, null=True)),
('is_verify', models.BooleanField(default=False)),
('account_type', models.CharField(choices=[('personal', 'Personal'), ('business', 'Business')], default='personal', max_length=255)),
('role', models.CharField(choices=[('superuser', 'Superuser'), ('admin', 'Admin'), ('user', 'User')], default='user', max_length=255)), ('role', models.CharField(choices=[('superuser', 'Superuser'), ('admin', 'Admin'), ('user', 'User')], default='user', max_length=255)),
('avatar', models.ImageField(default='avatars/default.png', upload_to='avatars/', verbose_name='Avatar')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
], ],
@@ -43,6 +91,45 @@ class Migration(migrations.Migration):
'abstract': False, 'abstract': False,
}, },
), ),
migrations.CreateModel(
name='Address',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='Name')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='addresses', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Address',
'verbose_name_plural': 'Addresses',
'db_table': 'address',
},
),
migrations.CreateModel(
name='Business',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='Business Name')),
('work_time', models.CharField(max_length=255, verbose_name='Work Time')),
('contact', models.CharField(max_length=255, verbose_name='Contact')),
('instagram', models.CharField(max_length=255, verbose_name='Instagram')),
('facebook', models.CharField(max_length=255, verbose_name='Facebook')),
('telegram', models.CharField(max_length=255, verbose_name='Telegram')),
('bio', models.TextField(verbose_name='Bio')),
('address', models.CharField(max_length=255, verbose_name='Address Name')),
('long', models.FloatField(verbose_name='Longitude')),
('lat', models.FloatField(verbose_name='Latitude')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='business', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Business',
'verbose_name_plural': 'Business',
'db_table': 'business',
},
),
migrations.CreateModel( migrations.CreateModel(
name='ResetToken', name='ResetToken',
fields=[ fields=[
@@ -57,4 +144,19 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Reset Tokens', 'verbose_name_plural': 'Reset Tokens',
}, },
), ),
migrations.CreateModel(
name='SearchHistory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('value', models.CharField(max_length=255, verbose_name='Search History')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Search History',
'verbose_name_plural': 'Search History',
'db_table': 'search_history',
},
),
] ]

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 *
from .ad import * # noqa from .banner import *
from .ad_items import * # noqa from .category import *
from .feedback import * # noqa from .common import * # noqa
from .banner import * # noqa from .feedback import *

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 django.contrib import admin
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin, TabularInline
from core.apps.api.models import AdModel, AdImage, AdVariant from core.apps.api.models import AdModel, AdImageModel, AdVariantModel
class AdImageInline(admin.TabularInline): class AdImageInline(TabularInline):
model = AdImage model = AdImageModel
extra = 1 extra = 1
tab = True
class AdVariantInline(admin.TabularInline): class AdVariantInline(TabularInline):
model = AdVariant model = AdVariantModel
extra = 1 extra = 1
tab = True
@admin.register(AdModel) @admin.register(AdModel)
class AdModelAdmin(ModelAdmin): class AdModelAdmin(ModelAdmin):
autocomplete_fields = ["tags"]
list_display = ( list_display = (
"id", "id",
"__str__", "__str__",

View File

@@ -1,10 +1,10 @@
from django.contrib import admin from django.contrib import admin
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from core.apps.api.models import AdImage from core.apps.api.models import AdImageModel
@admin.register(AdImage) @admin.register(AdImageModel)
class AdImageAdmin(ModelAdmin): class AdImageAdmin(ModelAdmin):
list_display = ( list_display = (
"id", "id",

View File

@@ -1,10 +1,10 @@
from django.contrib import admin from django.contrib import admin
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from core.apps.api.models import AdOption from core.apps.api.models import AdOptionModel
@admin.register(AdOption) @admin.register(AdOptionModel)
class AdOptionAdmin(ModelAdmin): class AdOptionAdmin(ModelAdmin):
list_display = ( list_display = (
"id", "id",

View File

@@ -1,10 +1,10 @@
from django.contrib import admin from django.contrib import admin
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from core.apps.api.models import AdVariant from core.apps.api.models import AdVariantModel
@admin.register(AdVariant) @admin.register(AdVariantModel)
class AdVariantAdmin(ModelAdmin): class AdVariantAdmin(ModelAdmin):
list_display = ( list_display = (
"id", "id",

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 django.contrib import admin
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from core.apps.api.models import Banner from core.apps.api.models import BannerModel
@admin.register(Banner) @admin.register(BannerModel)
class BannerAdmin(ModelAdmin): class BannerAdmin(ModelAdmin):
list_display = ( list_display = (
"id", "id",

View File

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

View File

@@ -1,10 +1,10 @@
from django.contrib import admin from django.contrib import admin
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from core.apps.api.models import Category from core.apps.api.models import CategoryModel
@admin.register(Category) @admin.register(CategoryModel)
class CategoryAdmin(ModelAdmin): class CategoryAdmin(ModelAdmin):
list_display = ( list_display = (
"id", "id",

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 django.contrib import admin
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from core.apps.api.models import Feedback, FeedbackImages from core.apps.api.models import FeedbackModel, FeedbackImageModel
@admin.register(Feedback) @admin.register(FeedbackModel)
class FeedbackAdmin(ModelAdmin): class FeedbackAdmin(ModelAdmin):
list_display = ( list_display = (
"id", "id",
@@ -12,7 +12,7 @@ class FeedbackAdmin(ModelAdmin):
) )
@admin.register(FeedbackImages) @admin.register(FeedbackImageModel)
class FeedbackImagesAdmin(ModelAdmin): class FeedbackImagesAdmin(ModelAdmin):
list_display = ( list_display = (
"id", "id",

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from django.db.models import ( from django.db.models import F, Case, When, FloatField, ExpressionWrapper, Subquery, OuterRef
F, Case, When, FloatField, ExpressionWrapper, Subquery, OuterRef from core.apps.api.models import AdVariantModel, AdModel
)
from core.apps.api.models import AdVariant, AdModel
class AdFilter(filters.FilterSet): class AdFilter(filters.FilterSet):
@@ -16,70 +14,28 @@ class AdFilter(filters.FilterSet):
has_discount = filters.BooleanFilter(method="filter_has_discount") has_discount = filters.BooleanFilter(method="filter_has_discount")
has_normal_user = filters.BooleanFilter(method="filter_has_normal_user") has_normal_user = filters.BooleanFilter(method="filter_has_normal_user")
has_business_user = filters.BooleanFilter(method="filter_has_business_user") has_business_user = filters.BooleanFilter(method="filter_has_business_user")
discount = filters.BooleanFilter(method="filter_discount")
class Meta: class Meta:
model = AdModel model = AdModel
fields = ["min_price", "max_price"] fields = ["min_price", "max_price"]
def filter_discount(self, queryset, name, value):
if value is True:
return queryset.filter(discount__gt=0)
return queryset.filter(discount=-1)
def filter_has_business_user(self, queryset, name, value): def filter_has_business_user(self, queryset, name, value):
return queryset.filter( return queryset.filter(user__account_type="business")
user__account_type="business"
)
def filter_has_normal_user(self, queryset, name, value): def filter_has_normal_user(self, queryset, name, value):
return queryset.filter( return queryset.filter(user__account_type="personal")
user__account_type="personal"
)
def filter_has_discount(self, queryset, name, value): def filter_has_discount(self, queryset, name, value):
return queryset.filter( return queryset.filter(variants__discount__gte=1).distinct()
variants__discount__gte=1
).distinct()
def filter_by_size(self, queryset, name, value): def filter_by_size(self, queryset, name, value):
return queryset.filter( return queryset.filter(variants__variant="Size", variants__value__iexact=value).distinct()
variants__variant="Size",
variants__value__iexact=value
).distinct()
def filter_by_color(self, queryset, name, value): def filter_by_color(self, queryset, name, value):
return queryset.filter( return queryset.filter(variants__variant="Color", variants__value__iexact=value).distinct()
variants__variant="Color",
variants__value__iexact=value
).distinct()
def filter_queryset(self, queryset):
variant_real_price_expr = Case(
When(discount=-1, then=F("price")),
When(
discount__gte=0,
then=ExpressionWrapper(
F("price") - (F("price") * F("discount") / 100),
output_field=FloatField()
)
),
output_field=FloatField()
)
cheapest_variant_qs = (
AdVariant.objects
.filter(ad=OuterRef("pk"))
.annotate(real_price=variant_real_price_expr)
.order_by("real_price")
.values("real_price")[:1]
)
ad_real_price = F("price")
queryset = queryset.annotate(
real_price=Case(
When(
variants__isnull=False,
then=Subquery(cheapest_variant_qs)
),
default=ad_real_price,
output_field=FloatField()
)
).distinct()
return super().filter_queryset(queryset)

View File

@@ -1,13 +1,13 @@
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from core.apps.api.models import Category from core.apps.api.models import CategoryModel
class CategoryFilter(filters.FilterSet): class CategoryFilter(filters.FilterSet):
# name = filters.CharFilter(field_name="name", lookup_expr="icontains") # name = filters.CharFilter(field_name="name", lookup_expr="icontains")
class Meta: class Meta:
model = Category model = CategoryModel
fields = [ fields = [
"show_home", "show_home",
"id", "id",

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 from django.db import migrations, models
@@ -8,27 +11,330 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('accounts', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Banner', name='AdTopPlanModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, unique=True, verbose_name='Plan Name')),
('price', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Price')),
('duration', models.PositiveIntegerField(verbose_name='Duration (days)')),
('description', models.TextField(blank=True, verbose_name='Description')),
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
],
options={
'verbose_name': 'Ad Top Plan',
'verbose_name_plural': 'Ad Top Plans',
'db_table': 'ad_top_plan',
'ordering': ['price'],
},
),
migrations.CreateModel(
name='BannerModel',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)), ('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)), ('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=255, verbose_name='Title')), ('title', models.CharField(max_length=255, verbose_name='Title')),
('description', models.TextField(verbose_name='Description')), ('description', models.TextField(verbose_name='Description')),
('mobile_image', models.ImageField(upload_to='banner/mobile_image/', verbose_name='Mobile Image')), ('mobile_image', models.ImageField(upload_to='banners/mobile/', verbose_name='Mobile Image')),
('desktop_image', models.ImageField(upload_to='banner/desktop_image/', verbose_name='Desktop Image')), ('desktop_image', models.ImageField(upload_to='banners/desktop/', verbose_name='Desktop Image')),
('link', models.URLField(verbose_name='Link')), ('link', models.URLField(verbose_name='Link')),
('bg_color', models.CharField(max_length=255, verbose_name='BG Color')), ('bg_color', models.CharField(default='#FFFFFF', max_length=7, verbose_name='Background Color')),
('text_color', models.CharField(max_length=255, verbose_name='Text Color')), ('text_color', models.CharField(default='#000000', max_length=7, verbose_name='Text Color')),
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
('order', models.PositiveIntegerField(default=0, verbose_name='Display Order')),
], ],
options={ options={
'verbose_name': 'Banner', 'verbose_name': 'Banner',
'verbose_name_plural': 'Banners', 'verbose_name_plural': 'Banners',
'db_table': 'banner', 'db_table': 'banner',
'ordering': ['order', '-created_at'],
}, },
), ),
migrations.CreateModel(
name='ColorModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, unique=True, verbose_name='Name')),
],
options={
'verbose_name': 'Color',
'verbose_name_plural': 'Colors',
'db_table': 'color',
'ordering': ['name'],
},
),
migrations.CreateModel(
name='TagsModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, unique=True, verbose_name='Tag Name')),
('slug', models.SlugField(max_length=255, unique=True, verbose_name='Slug')),
],
options={
'verbose_name': 'Tag',
'verbose_name_plural': 'Tags',
'db_table': 'tags',
'ordering': ['name'],
},
),
migrations.CreateModel(
name='AdModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='Name')),
('ad_type', models.CharField(choices=[('Buy', 'Buy'), ('Sell', 'Sell')], max_length=255, verbose_name='Type')),
('ad_category_type', models.CharField(choices=[('Product', 'Product'), ('Service', 'Service'), ('Auto', 'Auto'), ('Home', 'Home')], max_length=255, verbose_name='Category Type')),
('_price', models.DecimalField(blank=True, db_column='price', decimal_places=2, max_digits=10, null=True, verbose_name='Price')),
('discount', models.DecimalField(decimal_places=2, default=-1, max_digits=10, verbose_name='Discount')),
('is_available', models.BooleanField(default=True, verbose_name='Is available')),
('physical_product', models.BooleanField(default=False, verbose_name='Physical product')),
('image', models.ImageField(upload_to='ads/', verbose_name='Image')),
('description', models.TextField(verbose_name='Description')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ads', to=settings.AUTH_USER_MODEL, verbose_name='User')),
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.adtopplanmodel', verbose_name='Plan')),
],
options={
'verbose_name': 'Ad',
'verbose_name_plural': 'Ads',
'db_table': 'ad',
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='AdSizeModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('weight', models.PositiveIntegerField(verbose_name='Weight (g)')),
('width', models.PositiveIntegerField(verbose_name='Width (cm)')),
('height', models.PositiveIntegerField(verbose_name='Height (cm)')),
('length', models.PositiveIntegerField(verbose_name='Length (cm)')),
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='size_info', to='api.admodel', verbose_name='Ad')),
],
options={
'verbose_name': 'Ad Size',
'verbose_name_plural': 'Ad Sizes',
'db_table': 'ad_size',
},
),
migrations.CreateModel(
name='AdVariantModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('value', models.CharField(max_length=255, verbose_name='Value')),
('is_available', models.BooleanField(default=True, verbose_name='Is Available')),
('price', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Price')),
('stock_quantity', models.PositiveIntegerField(default=0, verbose_name='Stock Quantity')),
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variants', to='api.admodel', verbose_name='Ad')),
('color', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.colormodel', verbose_name='Color')),
],
options={
'verbose_name': 'Ad Variant',
'verbose_name_plural': 'Ad Variants',
'db_table': 'ad_variant',
},
),
migrations.CreateModel(
name='CategoryModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='Category Name')),
('name_uz', models.CharField(max_length=255, null=True, verbose_name='Category Name')),
('name_ru', models.CharField(max_length=255, null=True, verbose_name='Category Name')),
('name_en', models.CharField(max_length=255, null=True, verbose_name='Category Name')),
('show_home', models.BooleanField(default=False, verbose_name='Show on Home')),
('level', models.IntegerField(default=0, editable=False, verbose_name='Level')),
('image', models.ImageField(blank=True, null=True, upload_to='categories/', verbose_name='Image')),
('category_type', models.CharField(choices=[('Product', 'Product'), ('Service', 'Service'), ('Auto', 'Auto'), ('Home', 'Home')], default='Product', max_length=255, verbose_name='Category Type')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='api.categorymodel', verbose_name='Parent Category')),
],
options={
'verbose_name': 'Category',
'verbose_name_plural': 'Categories',
'db_table': 'category',
'ordering': ['level', 'name'],
},
),
migrations.AddField(
model_name='admodel',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.categorymodel', verbose_name='Category'),
),
migrations.CreateModel(
name='FeedbackModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('star', models.IntegerField(default=5, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)], verbose_name='Rating')),
('comment', models.TextField(max_length=1000, verbose_name='Comment')),
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='api.admodel', verbose_name='Ad')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Feedback',
'verbose_name_plural': 'Feedbacks',
'db_table': 'feedback',
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='FeedbackImageModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('image', models.ImageField(upload_to='feedbacks/', verbose_name='Image')),
('feedback', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.feedbackmodel', verbose_name='Feedback')),
],
options={
'verbose_name': 'Feedback Image',
'verbose_name_plural': 'Feedback Images',
'db_table': 'feedback_images',
},
),
migrations.CreateModel(
name='OrderModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('status', models.CharField(choices=[('Pending', 'Pending'), ('Cancel', 'Cancel'), ('Done', 'Done')], db_index=True, max_length=255, verbose_name='Status')),
('total_amount', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Total Amount')),
('address', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='accounts.address', verbose_name='Address')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Order',
'verbose_name_plural': 'Orders',
'db_table': 'order',
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='OrderItemModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')),
('quantity', models.PositiveIntegerField(default=1, verbose_name='Quantity')),
('ad', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='api.admodel', verbose_name='Ad')),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='api.ordermodel', verbose_name='Order')),
],
options={
'verbose_name': 'Order Item',
'verbose_name_plural': 'Order Items',
'db_table': 'order_item',
},
),
migrations.AddField(
model_name='admodel',
name='tags',
field=models.ManyToManyField(blank=True, to='api.tagsmodel', verbose_name='Tags'),
),
migrations.CreateModel(
name='AdOptionModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='Name')),
('value', models.CharField(max_length=255, verbose_name='Value')),
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='api.admodel', verbose_name='Ad')),
],
options={
'verbose_name': 'Ad Option',
'verbose_name_plural': 'Ad Options',
'db_table': 'ad_option',
'unique_together': {('ad', 'name')},
},
),
migrations.CreateModel(
name='AdImageModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('image', models.ImageField(upload_to='ads/images/', verbose_name='Image')),
('order', models.PositiveIntegerField(default=0, verbose_name='Display Order')),
('is_primary', models.BooleanField(default=False, verbose_name='Is Primary')),
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.admodel', verbose_name='Ad')),
('ad_variant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.advariantmodel', verbose_name='Ad Variant')),
],
options={
'verbose_name': 'Ad Image',
'verbose_name_plural': 'Ad Images',
'db_table': 'ad_images',
'ordering': ['order', '-created_at'],
'indexes': [models.Index(fields=['ad', 'is_primary'], name='ad_images_ad_id_ed2cb8_idx')],
},
),
migrations.AddIndex(
model_name='categorymodel',
index=models.Index(fields=['parent', 'show_home'], name='category_parent__ebe06e_idx'),
),
migrations.AddIndex(
model_name='categorymodel',
index=models.Index(fields=['level'], name='category_level_e67701_idx'),
),
migrations.AddIndex(
model_name='advariantmodel',
index=models.Index(fields=['ad', 'is_available'], name='ad_variant_ad_id_3a5d5e_idx'),
),
migrations.AlterUniqueTogether(
name='advariantmodel',
unique_together={('ad', 'value')},
),
migrations.AddIndex(
model_name='feedbackmodel',
index=models.Index(fields=['ad', '-created_at'], name='feedback_ad_id_444d80_idx'),
),
migrations.AddIndex(
model_name='feedbackmodel',
index=models.Index(fields=['user'], name='feedback_user_id_8cf53b_idx'),
),
migrations.AlterUniqueTogether(
name='feedbackmodel',
unique_together={('user', 'ad')},
),
migrations.AddIndex(
model_name='ordermodel',
index=models.Index(fields=['user', '-created_at'], name='order_user_id_13e363_idx'),
),
migrations.AddIndex(
model_name='ordermodel',
index=models.Index(fields=['status'], name='order_status_35c31c_idx'),
),
migrations.AlterUniqueTogether(
name='orderitemmodel',
unique_together={('order', 'ad')},
),
migrations.AddIndex(
model_name='admodel',
index=models.Index(fields=['-created_at'], name='ad_created_359de0_idx'),
),
migrations.AddIndex(
model_name='admodel',
index=models.Index(fields=['category', 'is_available'], name='ad_categor_8ff346_idx'),
),
] ]

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 .banner import *
from .feedback import * # noqa from .feedback import *
from .ad import * # noqa from .ad import *
from .ad_items import * # noqa from .common import *
from .order import * # noqa from .order import *

View File

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

View File

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

View File

@@ -2,26 +2,45 @@ from django.db import models
from django_core.models.base import AbstractBaseModel from django_core.models.base import AbstractBaseModel
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.apps.api.choices import AdCategoryType from core.apps.api.choices import AdCategoryType
from model_bakery import baker
class Category(AbstractBaseModel): class CategoryModel(AbstractBaseModel):
name = models.CharField(max_length=255, verbose_name=_('Category Name')) name = models.CharField(_("Category Name"), max_length=255)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children', on_delete=models.CASCADE) parent = models.ForeignKey(
show_home = models.BooleanField(default=False, verbose_name=_('Show Home')) "self",
level = models.IntegerField(default=0, verbose_name=_('Level')) null=True,
image = models.ImageField(verbose_name=_('Image'), null=True, blank=True) blank=True,
category_type = models.CharField(max_length=255, verbose_name=_('Category Type'), choices=AdCategoryType, related_name="children",
default=AdCategoryType.PRODUCT) on_delete=models.CASCADE,
verbose_name=_("Parent Category")
@classmethod )
def _baker(cls): show_home = models.BooleanField(_("Show on Home"), default=False)
return baker.make(cls) level = models.IntegerField(_("Level"), default=0, editable=False)
image = models.ImageField(_("Image"), upload_to="categories/", null=True, blank=True)
category_type = models.CharField(
_("Category Type"),
max_length=255,
choices=AdCategoryType,
default=AdCategoryType.PRODUCT
)
def __str__(self): def __str__(self):
return str(self.pk) return self.name
def save(self, *args, **kwargs):
"""Auto-calculate level based on parent"""
if self.parent:
self.level = self.parent.level + 1
else:
self.level = 0
super().save(*args, **kwargs)
class Meta: class Meta:
db_table = 'category' db_table = "category"
verbose_name = _('Category') verbose_name = _("Category")
verbose_name_plural = _('Categories') verbose_name_plural = _("Categories")
ordering = ["level", "name"]
indexes = [
models.Index(fields=["parent", "show_home"]),
models.Index(fields=["level"]),
]

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

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_core.models.base import AbstractBaseModel
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from core.apps.api.models.ad import AdModel from django.core.validators import MinValueValidator, MaxValueValidator
class Feedback(AbstractBaseModel): class FeedbackModel(AbstractBaseModel):
star = models.IntegerField(default=0, verbose_name=_("Star")) star = models.IntegerField(
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User")) _("Rating"),
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad"), related_name="feedback") default=5,
comment = models.CharField(max_length=255, verbose_name=_("Comment")) validators=[MinValueValidator(1), MaxValueValidator(5)]
)
user = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
verbose_name=_("User"),
related_name="feedbacks"
)
ad = models.ForeignKey(
"api.AdModel",
on_delete=models.CASCADE,
verbose_name=_("Ad"),
related_name="feedbacks"
)
comment = models.TextField(_("Comment"), max_length=1000)
def __str__(self): def __str__(self):
return str(self.pk) return f"{self.user.username} - {self.ad.name} ({self.star}★)"
class Meta: class Meta:
db_table = "feedback" db_table = "feedback"
verbose_name = _("Feedback") verbose_name = _("Feedback")
verbose_name_plural = _("Feedbacks") verbose_name_plural = _("Feedbacks")
ordering = ["-created_at"]
unique_together = [["user", "ad"]]
indexes = [
models.Index(fields=["ad", "-created_at"]),
models.Index(fields=["user"]),
]
class FeedbackImages(AbstractBaseModel): class FeedbackImageModel(AbstractBaseModel):
feedback = models.ForeignKey(Feedback, on_delete=models.CASCADE, verbose_name=_("Feedback")) feedback = models.ForeignKey(
image = models.ImageField(verbose_name=_("Image"), upload_to="feedback/" FeedbackModel,
"images/") on_delete=models.CASCADE,
verbose_name=_("Feedback"),
related_name="images"
)
image = models.ImageField(_("Image"), upload_to="feedbacks/")
def __str__(self): def __str__(self):
return str(self.pk) return f"Image for {self.feedback}"
class Meta: class Meta:
db_table = "feedback_images" db_table = "feedback_images"
verbose_name = _("Feedback Images") verbose_name = _("Feedback Image")
verbose_name_plural = _("Feedback Images") verbose_name_plural = _("Feedback Images")

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 django.contrib.auth import get_user_model
from core.apps.api.choices import OrderStatus from core.apps.api.choices import OrderStatus
from core.apps.accounts.models import Address from core.apps.accounts.models import Address
from core.apps.api.models import AdModel
class Order(AbstractBaseModel): class OrderModel(AbstractBaseModel):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User")) user = models.ForeignKey(
status = models.CharField(max_length=255, choices=OrderStatus) get_user_model(),
address = models.ForeignKey(Address, on_delete=models.CASCADE, verbose_name=_("Address")) on_delete=models.CASCADE,
verbose_name=_("User"),
related_name="orders"
)
status = models.CharField(
_("Status"),
max_length=255,
choices=OrderStatus,
db_index=True
)
address = models.ForeignKey(
Address,
on_delete=models.PROTECT,
verbose_name=_("Address")
)
total_amount = models.DecimalField(
_("Total Amount"),
max_digits=10,
decimal_places=2,
default=0
)
def __str__(self): def __str__(self):
return str(self.pk) return f"Order #{self.pk} - {self.user.username}"
def calculate_total(self):
"""Calculate total from order items"""
total = sum(item.subtotal for item in self.items.all())
self.total_amount = total
return total
class Meta: class Meta:
db_table = "order" db_table = "order"
verbose_name = _("Order") verbose_name = _("Order")
verbose_name_plural = _("Orders") verbose_name_plural = _("Orders")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["user", "-created_at"]),
models.Index(fields=["status"]),
]
class OrderItem(models.Model): class OrderItemModel(AbstractBaseModel):
order = models.ForeignKey(Order, on_delete=models.CASCADE, verbose_name=_("Order")) order = models.ForeignKey(
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("Price")) OrderModel,
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad")) on_delete=models.CASCADE,
count = models.PositiveIntegerField(default=0, verbose_name=_("Count")) verbose_name=_("Order"),
related_name="items"
)
ad = models.ForeignKey(
"api.AdModel",
on_delete=models.PROTECT,
verbose_name=_("Ad")
)
price = models.DecimalField(
_("Price"),
max_digits=10,
decimal_places=2
)
quantity = models.PositiveIntegerField(_("Quantity"), default=1)
def __str__(self): def __str__(self):
return str(self.pk) return f"{self.ad.name} x {self.quantity}"
@property
def subtotal(self):
"""Calculate item subtotal"""
return self.price * self.quantity
class Meta: class Meta:
db_table = "order_item" db_table = "order_item"
verbose_name = _("Order Item") verbose_name = _("Order Item")
verbose_name_plural = _("Order Items") verbose_name_plural = _("Order Items")
unique_together = [["order", "ad"]]

View File

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

View File

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

View File

@@ -1,270 +1,148 @@
from rest_framework import serializers from rest_framework import serializers
from django.db.models import Avg from django.db.models import Avg
from core.apps.accounts.choices import AccountType from core.apps.accounts.choices import AccountType
from core.apps.api.models import AdModel, AdVariant, Category, AdImage, AdOption from core.apps.api.models import AdModel, AdVariantModel, CategoryModel, AdImageModel, AdOptionModel
from core.apps.accounts.models import UserLike from core.apps.accounts.models import UserLike
from core.apps.api.choices import AdVariantType from core.apps.api.choices import AdVariantType
from core.apps.api.serializers.common.color import ListColorSerializer
from core.apps.api.serializers.common.size import ListSizeSerializer
class AdOptionSerializer(serializers.ModelSerializer): class AdOptionSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AdOption model = AdOptionModel
fields = [ fields = ["id", "name", "value"]
"id", read_only_fields = ["id"]
"name",
"value",
]
class CategorySerializer(serializers.ModelSerializer): class CategorySerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Category model = CategoryModel
fields = ["id", "name"] fields = ["id", "name"]
read_only_fields = ["id"]
class AdImageSerializer(serializers.ModelSerializer): class AdImageSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AdImage model = AdImageModel
fields = [ fields = ["id", "image", "ad_variant", "is_primary", "order"]
"image", read_only_fields = ["id"]
"ad_variant"
]
def to_representation(self, instance): def to_representation(self, instance):
data = super().to_representation(instance) data = super().to_representation(instance)
if not instance.ad_variant:
if instance.ad_variant is None:
data.pop("ad_variant", None) data.pop("ad_variant", None)
return data return data
class AdVariantSerializer(serializers.ModelSerializer): class AdVariantSerializer(serializers.ModelSerializer):
color = ListColorSerializer()
size = ListSizeSerializer()
class Meta: class Meta:
model = AdVariant model = AdVariantModel
fields = [ fields = ["id", "size", "color", "is_available", "price", "stock_quantity"]
"id", read_only_fields = ["id", "color_name"]
"variant",
"value",
"is_available",
"price",
"discount",
]
class BaseAdSerializer(serializers.ModelSerializer): class AdListSerializer(serializers.ModelSerializer):
is_liked = serializers.SerializerMethodField() is_liked = serializers.SerializerMethodField()
star = serializers.SerializerMethodField() rating = serializers.SerializerMethodField()
comment_count = serializers.SerializerMethodField() reviews_count = serializers.SerializerMethodField()
class Meta: class Meta:
model = AdModel model = AdModel
fields = [ fields = ["id", "name", "price", "image", "discount", "is_liked", "rating", "reviews_count", "is_available"]
"id", read_only_fields = fields
"name",
"price",
"image",
"is_liked",
"star",
"comment_count",
]
def get_star(self, obj): def get_rating(self, obj):
avg = obj.feedback.aggregate(avg=Avg("star"))["avg"] """Get average rating from feedbacks"""
return avg or 0 avg = obj.feedbacks.aggregate(avg=Avg("star"))["avg"]
return round(avg, 1) if avg else 0
def get_comment_count(self, obj): def get_reviews_count(self, obj):
count = obj.feedback.count() """Get total count of feedbacks"""
return count or 0 return obj.feedbacks.count()
def get_is_liked(self, obj): def get_is_liked(self, obj):
"""Check if current user liked this ad"""
request = self.context.get("request") request = self.context.get("request")
user = getattr(request, "user", None) if not request or not request.user.is_authenticated:
if not user or not user.is_authenticated:
return False return False
return UserLike.objects.filter(user=request.user, ad=obj).exists()
return UserLike.objects.filter(user=user, ad=obj).exists()
class ListAdSerializer(BaseAdSerializer): class AdDetailSerializer(AdListSerializer):
price = serializers.SerializerMethodField()
discount = serializers.SerializerMethodField()
class Meta(BaseAdSerializer.Meta):
fields = [
"id",
"name",
"price",
"image",
"star",
"comment_count",
"discount",
"is_liked",
]
def _get_first_variant(self, obj):
if not hasattr(self, "_variant_cache"):
self._variant_cache = {}
if obj.id not in self._variant_cache:
self._variant_cache[obj.id] = obj.variants.order_by("price").first()
return self._variant_cache[obj.id]
def get_price(self, obj):
variant = self._get_first_variant(obj)
if not variant:
return obj.price
return variant.price if variant else 0
def get_discount(self, obj):
variant = self._get_first_variant(obj)
return variant.discount if variant else -1.0
class FullListAdSerializer(serializers.Serializer):
ads = ListAdSerializer(many=True)
categories = serializers.SerializerMethodField()
colors = serializers.SerializerMethodField()
sizes = serializers.SerializerMethodField()
min_price = serializers.SerializerMethodField()
max_price = serializers.SerializerMethodField()
def get_categories(self, obj):
ads = obj.get("ads", [])
category_ids = set()
categories = []
for ad in ads:
category = ad.category
if category and category.id not in category_ids:
category_ids.add(category.id)
categories.append(category)
return CategorySerializer(categories, many=True).data
def get_colors(self, obj):
ads = obj.get("ads", [])
color_values = set()
for ad in ads:
variants = getattr(ad, "variants", [])
for v in variants.all():
if v.variant == AdVariantType.COLOR:
color_values.add(v.value)
return list(color_values)
def get_sizes(self, obj):
ads = obj.get("ads", [])
size_values = set()
for ad in ads:
variants = getattr(ad, "variants", [])
for v in variants.all():
if v.variant == AdVariantType.SIZE:
size_values.add(v.value)
return list(size_values)
def get_min_price(self, obj):
ads = obj.get("ads", [])
prices = []
for ad in ads:
ad_data = ListAdSerializer(ad, context=self.context).data
price = ad_data.get("price")
if price is not None:
prices.append(price)
return min(prices) if prices else None
def get_max_price(self, obj):
ads = obj.get("ads", [])
prices = []
for ad in ads:
ad_data = ListAdSerializer(ad, context=self.context).data
price = ad_data.get("price")
if price is not None:
prices.append(price)
return max(prices) if prices else None
class RetrieveAdSerializer(BaseAdSerializer):
variants = AdVariantSerializer(many=True, read_only=True) variants = AdVariantSerializer(many=True, read_only=True)
images = serializers.SerializerMethodField() images = AdImageSerializer(many=True, read_only=True)
colors = serializers.SerializerMethodField() colors = serializers.SerializerMethodField()
sizes = serializers.SerializerMethodField() sizes = serializers.SerializerMethodField()
creator = serializers.SerializerMethodField() creator = serializers.SerializerMethodField()
options = AdOptionSerializer(many=True, read_only=True) options = AdOptionSerializer(many=True, read_only=True)
category = CategorySerializer(read_only=True)
class Meta(BaseAdSerializer.Meta): class Meta(AdListSerializer.Meta):
fields = [ fields = AdListSerializer.Meta.fields + [
"id", "description",
"name",
"price",
"image",
"star",
"comment_count",
"is_liked",
"images", "images",
"variants", "variants",
"colors", "colors",
"sizes", "sizes",
"creator", "creator",
"description", "options",
"options" "category",
"ad_type",
"physical_product",
] ]
def get_images(self, obj):
objects = obj.images.all()
return AdImageSerializer(objects, many=True, context=self.context).data
def get_colors(self, obj): def get_colors(self, obj):
color_values = set() """Get unique colors from variants"""
return list(obj.variants.values_list("color", flat=True).distinct())
variants = getattr(obj, "variants", [])
for v in variants.all():
if v.variant == AdVariantType.COLOR:
color_values.add(v.value)
return list(color_values)
def get_sizes(self, obj): def get_sizes(self, obj):
size_values = set() """Get unique sizes from variants"""
return list(obj.variants.values_list("size", flat=True).distinct())
variants = getattr(obj, "variants", [])
for v in variants.all():
if v.variant == AdVariantType.SIZE:
size_values.add(v.value)
return list(size_values)
def get_creator(self, obj): def get_creator(self, obj):
"""Get creator information"""
user = obj.user user = obj.user
user_type = user.account_type
request = self.context.get("request") request = self.context.get("request")
avatar_url = request.build_absolute_uri(user.avatar.url) if user.avatar else None avatar_url = None
if user.avatar and request:
avatar_url = request.build_absolute_uri(user.avatar.url)
if user_type == AccountType.BUSINESS: if user.account_type == AccountType.BUSINESS:
return { username = user.business.name if hasattr(user, "business") else user.username
"username": user.business.name,
"avatar": avatar_url,
"create_at": user.validated_at,
"last_live": "endi qo'shamiz! waiting pls ))"
}
else: else:
username = f"{user.first_name} {user.last_name}" username = f"{user.first_name} {user.last_name}".strip() or user.username
return { return {
"id": user.id,
"username": username, "username": username,
"avatar": avatar_url, "avatar": avatar_url,
"create_at": user.validated_at, "account_type": user.account_type,
"last_live": "endi qo'shamiz! waiting pls ))" "joined_at": user.date_joined,
} }
class CreateAdSerializer(BaseAdSerializer): class AdCreateSerializer(serializers.ModelSerializer):
class Meta(BaseAdSerializer.Meta): ... class Meta:
model = AdModel
fields = [
"name",
"ad_type",
"category",
"ad_category_type",
"discount",
"is_available",
"physical_product",
"plan",
"tags",
"image",
"description",
]
def create(self, validated_data):
validated_data["user"] = self.context["request"].user
return super().create(validated_data)

View File

@@ -1,26 +1,22 @@
from rest_framework import serializers from rest_framework import serializers
from django.db.models import Avg from django.db.models import Avg
from core.apps.api.models import AdModel, AdVariant from core.apps.api.models import AdModel, AdVariantModel
from core.apps.accounts.models import UserLike from core.apps.accounts.models import UserLike
class AdVariantSerializer(serializers.ModelSerializer): class AdVariantSerializer(serializers.ModelSerializer):
color_name = serializers.CharField(source="color.name", read_only=True)
class Meta: class Meta:
model = AdVariant model = AdVariantModel
fields = [ fields = ["id", "value", "color_name", "is_available", "price"]
"variant", read_only_fields = fields
"value",
"is_available",
"price",
"discount",
]
class BaseHomeAdSerializer(serializers.ModelSerializer): class HomeAdListSerializer(serializers.ModelSerializer):
star = serializers.SerializerMethodField() """Optimized serializer for home page ad listing"""
comment_count = serializers.SerializerMethodField() rating = serializers.SerializerMethodField()
price = serializers.SerializerMethodField() reviews_count = serializers.SerializerMethodField()
discount = serializers.SerializerMethodField()
is_liked = serializers.SerializerMethodField() is_liked = serializers.SerializerMethodField()
class Meta: class Meta:
@@ -30,54 +26,25 @@ class BaseHomeAdSerializer(serializers.ModelSerializer):
"name", "name",
"price", "price",
"image", "image",
"star", "rating",
"comment_count", "reviews_count",
"discount", "discount",
"is_liked", "is_liked",
] ]
read_only_fields = fields
def _get_first_variant(self, obj): def get_rating(self, obj):
if not hasattr(self, "_variant_cache"): """Get average rating"""
self._variant_cache = {} avg = obj.feedbacks.aggregate(avg=Avg("star"))["avg"]
if obj.id not in self._variant_cache: return round(avg, 1) if avg else 0
self._variant_cache[obj.id] = obj.variants.order_by("price").first()
return self._variant_cache[obj.id]
def get_price(self, obj): def get_reviews_count(self, obj):
variant = self._get_first_variant(obj) """Get feedback count"""
if not variant: return obj.feedbacks.count()
return obj.price
return variant.price if variant else 0
def get_discount(self, obj):
variant = self._get_first_variant(obj)
return variant.discount if variant else -1.0
def get_star(self, obj):
avg = obj.feedback.aggregate(avg=Avg("star"))["avg"]
return avg or 0
def get_comment_count(self, obj):
count = obj.feedback.count()
return count or 0
def get_is_liked(self, obj): def get_is_liked(self, obj):
"""Check if user liked this ad"""
request = self.context.get("request") request = self.context.get("request")
user = getattr(request, "user", None) if not request or not request.user.is_authenticated:
if not user or not user.is_authenticated:
return False return False
return UserLike.objects.filter(user=request.user, ad=obj).exists()
return UserLike.objects.filter(user=user, ad=obj).exists()
class ListHomeAdSerializer(BaseHomeAdSerializer):
class Meta(BaseHomeAdSerializer.Meta): ...
class RetrieveHomeAdSerializer(BaseHomeAdSerializer):
class Meta(BaseHomeAdSerializer.Meta): ...
class CreateHomeAdSerializer(BaseHomeAdSerializer):
class Meta(BaseHomeAdSerializer.Meta): ...

View File

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

View File

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

View File

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

View File

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

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 .history import *
from .search_ads import * # noqa 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