From 6d176efece4db4bf596b5d02912aaab02b849106 Mon Sep 17 00:00:00 2001 From: muhammadvadud Date: Tue, 25 Nov 2025 16:40:18 +0500 Subject: [PATCH] Home api lari chiqarildi --- core/apps/api/admin/__init__.py | 3 + core/apps/api/admin/ad/__init__.py | 1 + core/apps/api/admin/ad/ad.py | 23 ++++++++ core/apps/api/admin/ad_items/__init__.py | 4 ++ core/apps/api/admin/ad_items/ad_images.py | 12 ++++ core/apps/api/admin/ad_items/ad_top_plan.py | 12 ++++ core/apps/api/admin/ad_items/ad_variant.py | 12 ++++ core/apps/api/admin/ad_items/tags.py | 12 ++++ core/apps/api/admin/feedback/__init__.py | 1 + core/apps/api/admin/feedback/feedback.py | 20 +++++++ core/apps/api/migrations/0005_admodel_star.py | 18 ++++++ .../api/migrations/0006_alter_adimage_ad.py | 19 ++++++ .../api/migrations/0007_alter_advariant_ad.py | 19 ++++++ .../api/migrations/0008_adimage_ad_variant.py | 19 ++++++ .../0009_alter_adimage_ad_variant.py | 19 ++++++ .../0010_remove_admodel_star_admodel_image.py | 23 ++++++++ .../api/migrations/0011_alter_feedback_ad.py | 19 ++++++ .../0012_rename_command_feedback_comment.py | 18 ++++++ core/apps/api/models/ad/ad.py | 1 + core/apps/api/models/ad/category.py | 7 --- core/apps/api/models/ad_items/ad_images.py | 9 ++- core/apps/api/models/ad_items/ad_variant.py | 2 +- core/apps/api/models/feedback/feedback.py | 4 +- core/apps/api/serializers/__init__.py | 1 + core/apps/api/serializers/ad/__init__.py | 1 + core/apps/api/serializers/ad/home_api.py | 59 +++++++++++++++++++ core/apps/api/urls.py | 3 +- core/apps/api/views/__init__.py | 2 + core/apps/api/views/ad/__init__.py | 1 + core/apps/api/views/ad/home_api.py | 24 ++++++++ 30 files changed, 355 insertions(+), 13 deletions(-) create mode 100644 core/apps/api/admin/ad/__init__.py create mode 100644 core/apps/api/admin/ad/ad.py create mode 100644 core/apps/api/admin/ad_items/__init__.py create mode 100644 core/apps/api/admin/ad_items/ad_images.py create mode 100644 core/apps/api/admin/ad_items/ad_top_plan.py create mode 100644 core/apps/api/admin/ad_items/ad_variant.py create mode 100644 core/apps/api/admin/ad_items/tags.py create mode 100644 core/apps/api/admin/feedback/__init__.py create mode 100644 core/apps/api/admin/feedback/feedback.py create mode 100644 core/apps/api/migrations/0005_admodel_star.py create mode 100644 core/apps/api/migrations/0006_alter_adimage_ad.py create mode 100644 core/apps/api/migrations/0007_alter_advariant_ad.py create mode 100644 core/apps/api/migrations/0008_adimage_ad_variant.py create mode 100644 core/apps/api/migrations/0009_alter_adimage_ad_variant.py create mode 100644 core/apps/api/migrations/0010_remove_admodel_star_admodel_image.py create mode 100644 core/apps/api/migrations/0011_alter_feedback_ad.py create mode 100644 core/apps/api/migrations/0012_rename_command_feedback_comment.py create mode 100644 core/apps/api/serializers/ad/__init__.py create mode 100644 core/apps/api/serializers/ad/home_api.py create mode 100644 core/apps/api/views/ad/__init__.py create mode 100644 core/apps/api/views/ad/home_api.py diff --git a/core/apps/api/admin/__init__.py b/core/apps/api/admin/__init__.py index d63c50f..6929f87 100644 --- a/core/apps/api/admin/__init__.py +++ b/core/apps/api/admin/__init__.py @@ -1 +1,4 @@ from .category import * # noqa +from .ad import * # noqa +from .ad_items import * # noqa +from .feedback import * # noqa diff --git a/core/apps/api/admin/ad/__init__.py b/core/apps/api/admin/ad/__init__.py new file mode 100644 index 0000000..35176b7 --- /dev/null +++ b/core/apps/api/admin/ad/__init__.py @@ -0,0 +1 @@ +from .ad import * # noqa diff --git a/core/apps/api/admin/ad/ad.py b/core/apps/api/admin/ad/ad.py new file mode 100644 index 0000000..c9a921d --- /dev/null +++ b/core/apps/api/admin/ad/ad.py @@ -0,0 +1,23 @@ +from django.contrib import admin +from unfold.admin import ModelAdmin + +from core.apps.api.models import AdModel, AdImage, AdVariant + + +class AdImageInline(admin.TabularInline): + model = AdImage + extra = 1 + + +class AdVariantInline(admin.TabularInline): + model = AdVariant + extra = 1 + + +@admin.register(AdModel) +class AdModelAdmin(ModelAdmin): + list_display = ( + "id", + "__str__", + ) + inlines = [AdImageInline, AdVariantInline] diff --git a/core/apps/api/admin/ad_items/__init__.py b/core/apps/api/admin/ad_items/__init__.py new file mode 100644 index 0000000..6598a6f --- /dev/null +++ b/core/apps/api/admin/ad_items/__init__.py @@ -0,0 +1,4 @@ +from .tags import * # noqa +from .ad_top_plan import * # noqa +from .ad_images import * # noqa +from .ad_variant import * # noqa diff --git a/core/apps/api/admin/ad_items/ad_images.py b/core/apps/api/admin/ad_items/ad_images.py new file mode 100644 index 0000000..6e073bb --- /dev/null +++ b/core/apps/api/admin/ad_items/ad_images.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from unfold.admin import ModelAdmin + +from core.apps.api.models import AdImage + + +@admin.register(AdImage) +class AdImageAdmin(ModelAdmin): + list_display = ( + "id", + "__str__", + ) diff --git a/core/apps/api/admin/ad_items/ad_top_plan.py b/core/apps/api/admin/ad_items/ad_top_plan.py new file mode 100644 index 0000000..b5b49d2 --- /dev/null +++ b/core/apps/api/admin/ad_items/ad_top_plan.py @@ -0,0 +1,12 @@ +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__", + ) diff --git a/core/apps/api/admin/ad_items/ad_variant.py b/core/apps/api/admin/ad_items/ad_variant.py new file mode 100644 index 0000000..7f60516 --- /dev/null +++ b/core/apps/api/admin/ad_items/ad_variant.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from unfold.admin import ModelAdmin + +from core.apps.api.models import AdVariant + + +@admin.register(AdVariant) +class AdVariantAdmin(ModelAdmin): + list_display = ( + "id", + "__str__", + ) diff --git a/core/apps/api/admin/ad_items/tags.py b/core/apps/api/admin/ad_items/tags.py new file mode 100644 index 0000000..f59449a --- /dev/null +++ b/core/apps/api/admin/ad_items/tags.py @@ -0,0 +1,12 @@ +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__", + ) diff --git a/core/apps/api/admin/feedback/__init__.py b/core/apps/api/admin/feedback/__init__.py new file mode 100644 index 0000000..5e1a07c --- /dev/null +++ b/core/apps/api/admin/feedback/__init__.py @@ -0,0 +1 @@ +from .feedback import * # noqa diff --git a/core/apps/api/admin/feedback/feedback.py b/core/apps/api/admin/feedback/feedback.py new file mode 100644 index 0000000..d870ca0 --- /dev/null +++ b/core/apps/api/admin/feedback/feedback.py @@ -0,0 +1,20 @@ +from django.contrib import admin +from unfold.admin import ModelAdmin + +from core.apps.api.models import Feedback, FeedbackImages + + +@admin.register(Feedback) +class FeedbackAdmin(ModelAdmin): + list_display = ( + "id", + "__str__", + ) + + +@admin.register(FeedbackImages) +class FeedbackImagesAdmin(ModelAdmin): + list_display = ( + "id", + "__str__", + ) diff --git a/core/apps/api/migrations/0005_admodel_star.py b/core/apps/api/migrations/0005_admodel_star.py new file mode 100644 index 0000000..bff48f1 --- /dev/null +++ b/core/apps/api/migrations/0005_admodel_star.py @@ -0,0 +1,18 @@ +# 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'), + ), + ] diff --git a/core/apps/api/migrations/0006_alter_adimage_ad.py b/core/apps/api/migrations/0006_alter_adimage_ad.py new file mode 100644 index 0000000..b4fa955 --- /dev/null +++ b/core/apps/api/migrations/0006_alter_adimage_ad.py @@ -0,0 +1,19 @@ +# 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'), + ), + ] diff --git a/core/apps/api/migrations/0007_alter_advariant_ad.py b/core/apps/api/migrations/0007_alter_advariant_ad.py new file mode 100644 index 0000000..e98da00 --- /dev/null +++ b/core/apps/api/migrations/0007_alter_advariant_ad.py @@ -0,0 +1,19 @@ +# 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'), + ), + ] diff --git a/core/apps/api/migrations/0008_adimage_ad_variant.py b/core/apps/api/migrations/0008_adimage_ad_variant.py new file mode 100644 index 0000000..f66b8f5 --- /dev/null +++ b/core/apps/api/migrations/0008_adimage_ad_variant.py @@ -0,0 +1,19 @@ +# 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'), + ), + ] diff --git a/core/apps/api/migrations/0009_alter_adimage_ad_variant.py b/core/apps/api/migrations/0009_alter_adimage_ad_variant.py new file mode 100644 index 0000000..ff64c1c --- /dev/null +++ b/core/apps/api/migrations/0009_alter_adimage_ad_variant.py @@ -0,0 +1,19 @@ +# 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'), + ), + ] diff --git a/core/apps/api/migrations/0010_remove_admodel_star_admodel_image.py b/core/apps/api/migrations/0010_remove_admodel_star_admodel_image.py new file mode 100644 index 0000000..77658f9 --- /dev/null +++ b/core/apps/api/migrations/0010_remove_admodel_star_admodel_image.py @@ -0,0 +1,23 @@ +# 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, + ), + ] diff --git a/core/apps/api/migrations/0011_alter_feedback_ad.py b/core/apps/api/migrations/0011_alter_feedback_ad.py new file mode 100644 index 0000000..bf9f1c9 --- /dev/null +++ b/core/apps/api/migrations/0011_alter_feedback_ad.py @@ -0,0 +1,19 @@ +# 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'), + ), + ] diff --git a/core/apps/api/migrations/0012_rename_command_feedback_comment.py b/core/apps/api/migrations/0012_rename_command_feedback_comment.py new file mode 100644 index 0000000..55fe778 --- /dev/null +++ b/core/apps/api/migrations/0012_rename_command_feedback_comment.py @@ -0,0 +1,18 @@ +# 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', + ), + ] diff --git a/core/apps/api/models/ad/ad.py b/core/apps/api/models/ad/ad.py index 508c480..968ca19 100644 --- a/core/apps/api/models/ad/ad.py +++ b/core/apps/api/models/ad/ad.py @@ -16,6 +16,7 @@ class AdModel(AbstractBaseModel): physical_product = models.BooleanField(verbose_name=_("Physical product"), default=False) plan = models.ForeignKey("api.AdTopPlan", on_delete=models.CASCADE, verbose_name=_("Plan")) tags = models.ManyToManyField("api.Tags", verbose_name=_("Tags")) + image = models.ImageField(verbose_name=_("Image")) def __str__(self): return str(self.pk) diff --git a/core/apps/api/models/ad/category.py b/core/apps/api/models/ad/category.py index 224d22d..cde18bb 100644 --- a/core/apps/api/models/ad/category.py +++ b/core/apps/api/models/ad/category.py @@ -13,13 +13,6 @@ class Category(AbstractBaseModel): category_type = models.CharField(max_length=255, verbose_name=_('Category Type'), choices=AdCategoryType, default=AdCategoryType.PRODUCT) - def save(self, *args, **kwargs): - if self.parent: - self.level = self.parent.level + 1 - else: - self.level = 0 - super().save(*args, **kwargs) - def __str__(self): return str(self.pk) diff --git a/core/apps/api/models/ad_items/ad_images.py b/core/apps/api/models/ad_items/ad_images.py index c8bf117..49c19d3 100644 --- a/core/apps/api/models/ad_items/ad_images.py +++ b/core/apps/api/models/ad_items/ad_images.py @@ -1,12 +1,17 @@ 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.ad.ad import AdModel + +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"), on_delete=models.CASCADE) + 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) diff --git a/core/apps/api/models/ad_items/ad_variant.py b/core/apps/api/models/ad_items/ad_variant.py index 28644d7..a67f961 100644 --- a/core/apps/api/models/ad_items/ad_variant.py +++ b/core/apps/api/models/ad_items/ad_variant.py @@ -6,7 +6,7 @@ from core.apps.api.choices.ad_variant_type import AdVariantType class AdVariant(AbstractBaseModel): - ad = models.ForeignKey(AdModel, on_delete=models.CASCADE) + 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) diff --git a/core/apps/api/models/feedback/feedback.py b/core/apps/api/models/feedback/feedback.py index b54a5df..c8ba25e 100644 --- a/core/apps/api/models/feedback/feedback.py +++ b/core/apps/api/models/feedback/feedback.py @@ -8,8 +8,8 @@ from core.apps.api.models.ad import AdModel class Feedback(AbstractBaseModel): star = models.IntegerField(default=0, verbose_name=_("Star")) user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User")) - ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad")) - command = models.CharField(max_length=255, verbose_name=_("Command")) + ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad"), related_name="feedback") + comment = models.CharField(max_length=255, verbose_name=_("Comment")) def __str__(self): return str(self.pk) diff --git a/core/apps/api/serializers/__init__.py b/core/apps/api/serializers/__init__.py index 5da6a0a..42b47f4 100644 --- a/core/apps/api/serializers/__init__.py +++ b/core/apps/api/serializers/__init__.py @@ -1,2 +1,3 @@ from .category import * # noqa from .search import * # noqa +from .ad import * # noqa diff --git a/core/apps/api/serializers/ad/__init__.py b/core/apps/api/serializers/ad/__init__.py new file mode 100644 index 0000000..77f06da --- /dev/null +++ b/core/apps/api/serializers/ad/__init__.py @@ -0,0 +1 @@ +from .home_api import * # noqa diff --git a/core/apps/api/serializers/ad/home_api.py b/core/apps/api/serializers/ad/home_api.py new file mode 100644 index 0000000..67f7ba3 --- /dev/null +++ b/core/apps/api/serializers/ad/home_api.py @@ -0,0 +1,59 @@ +from rest_framework import serializers +from django.db.models import Avg +from core.apps.api.models import AdModel, AdVariant + + +class AdVariantSerializer(serializers.ModelSerializer): + class Meta: + model = AdVariant + fields = [ + "variant", + "value", + "is_available", + "price", + "discount", + ] + + +class BaseHomeAdSerializer(serializers.ModelSerializer): + variants = serializers.SerializerMethodField() + star = serializers.SerializerMethodField() + comment_count = serializers.SerializerMethodField() + + class Meta: + model = AdModel + fields = [ + "id", + "name", + "price", + "image", + "star", + "comment_count", + "variants", + ] + + def get_variants(self, obj): + variant = obj.variants.order_by("price").first() + if variant: + return AdVariantSerializer(variant).data + return [] + + 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 + + +class ListHomeAdSerializer(BaseHomeAdSerializer): + class Meta(BaseHomeAdSerializer.Meta): ... + + +class RetrieveHomeAdSerializer(BaseHomeAdSerializer): + class Meta(BaseHomeAdSerializer.Meta): ... + + +class CreateHomeAdSerializer(BaseHomeAdSerializer): + class Meta(BaseHomeAdSerializer.Meta): ... diff --git a/core/apps/api/urls.py b/core/apps/api/urls.py index 76446df..6b04a93 100644 --- a/core/apps/api/urls.py +++ b/core/apps/api/urls.py @@ -1,9 +1,10 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from core.apps.api.views import CategoryViewSet, SearchHistoryViewSet +from core.apps.api.views import CategoryViewSet, SearchHistoryViewSet, HomeAdApiView router = DefaultRouter() router.register("category", CategoryViewSet, basename="category") router.register("search-history", SearchHistoryViewSet, basename="search-history") +router.register("home-ad", HomeAdApiView, basename="home-ad") urlpatterns = [path("", include(router.urls))] diff --git a/core/apps/api/views/__init__.py b/core/apps/api/views/__init__.py index 5da6a0a..ee882c4 100644 --- a/core/apps/api/views/__init__.py +++ b/core/apps/api/views/__init__.py @@ -1,2 +1,4 @@ from .category import * # noqa from .search import * # noqa +from .ad import * # noqa + diff --git a/core/apps/api/views/ad/__init__.py b/core/apps/api/views/ad/__init__.py new file mode 100644 index 0000000..77f06da --- /dev/null +++ b/core/apps/api/views/ad/__init__.py @@ -0,0 +1 @@ +from .home_api import * # noqa diff --git a/core/apps/api/views/ad/home_api.py b/core/apps/api/views/ad/home_api.py new file mode 100644 index 0000000..f69eef5 --- /dev/null +++ b/core/apps/api/views/ad/home_api.py @@ -0,0 +1,24 @@ +from rest_framework.viewsets import ReadOnlyModelViewSet +from rest_framework.permissions import AllowAny +from drf_spectacular.utils import extend_schema +from django_core.mixins import BaseViewSetMixin +from core.apps.api.models import AdModel +from core.apps.api.serializers.ad.home_api import ( + ListHomeAdSerializer, + CreateHomeAdSerializer, + RetrieveHomeAdSerializer, +) + + +@extend_schema(tags=["Home Ad Api"]) +class HomeAdApiView(BaseViewSetMixin, ReadOnlyModelViewSet): + queryset = AdModel.objects.all() + serializer_class = ListHomeAdSerializer + permission_classes = [AllowAny] + + action_permission_classes = {} + action_serializer_class = { + "list": ListHomeAdSerializer, + "retrieve": RetrieveHomeAdSerializer, + "create": CreateHomeAdSerializer, + }