From 37a16900f7fa44de8c6623d86ca47a4aa19aa18a Mon Sep 17 00:00:00 2001 From: muhammadvadud Date: Mon, 24 Nov 2025 15:57:46 +0500 Subject: [PATCH 1/3] Category Api lari tayyor --- config/urls.py | 4 +- core/apps/api/admin/__init__.py | 1 + core/apps/api/admin/category/__init__.py | 1 + core/apps/api/admin/category/category.py | 12 +++++ core/apps/api/filters/__init__.py | 1 + core/apps/api/filters/category.py | 14 ++++++ .../api/migrations/0003_category_image.py | 18 ++++++++ core/apps/api/models/ad/category.py | 8 ++++ core/apps/api/serializers/__init__.py | 1 + .../apps/api/serializers/category/__init__.py | 1 + .../apps/api/serializers/category/category.py | 44 ++++++++++++++++++ core/apps/api/urls.py | 11 +++-- core/apps/api/views/__init__.py | 1 + core/apps/api/views/category/__init__.py | 1 + core/apps/api/views/category/category.py | 45 +++++++++++++++++++ core/apps/shared/urls.py | 9 ++-- test.py | 41 +++++++++++++++++ 17 files changed, 199 insertions(+), 14 deletions(-) create mode 100644 core/apps/api/admin/category/__init__.py create mode 100644 core/apps/api/admin/category/category.py create mode 100644 core/apps/api/filters/__init__.py create mode 100644 core/apps/api/filters/category.py create mode 100644 core/apps/api/migrations/0003_category_image.py create mode 100644 core/apps/api/serializers/category/__init__.py create mode 100644 core/apps/api/serializers/category/category.py create mode 100644 core/apps/api/views/category/__init__.py create mode 100644 core/apps/api/views/category/category.py create mode 100644 test.py diff --git a/config/urls.py b/config/urls.py index cf3b0af..693ca26 100644 --- a/config/urls.py +++ b/config/urls.py @@ -19,8 +19,8 @@ def home(request): urlpatterns = [ path("health/", home), path("", include("core.apps.accounts.urls")), - path("api/", include("core.apps.shared.urls")), - path("api/", include("core.apps.api.urls")), + path("api/v1/", include("core.apps.shared.urls")), + path("api/v1/", include("core.apps.api.urls")), ] urlpatterns += [ path("admin/", admin.site.urls), diff --git a/core/apps/api/admin/__init__.py b/core/apps/api/admin/__init__.py index e69de29..d63c50f 100644 --- a/core/apps/api/admin/__init__.py +++ b/core/apps/api/admin/__init__.py @@ -0,0 +1 @@ +from .category import * # noqa diff --git a/core/apps/api/admin/category/__init__.py b/core/apps/api/admin/category/__init__.py new file mode 100644 index 0000000..d63c50f --- /dev/null +++ b/core/apps/api/admin/category/__init__.py @@ -0,0 +1 @@ +from .category import * # noqa diff --git a/core/apps/api/admin/category/category.py b/core/apps/api/admin/category/category.py new file mode 100644 index 0000000..5cffbde --- /dev/null +++ b/core/apps/api/admin/category/category.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from unfold.admin import ModelAdmin + +from core.apps.api.models import Category + + +@admin.register(Category) +class CategoryAdmin(ModelAdmin): + list_display = ( + "id", + "__str__", + ) diff --git a/core/apps/api/filters/__init__.py b/core/apps/api/filters/__init__.py new file mode 100644 index 0000000..d63c50f --- /dev/null +++ b/core/apps/api/filters/__init__.py @@ -0,0 +1 @@ +from .category import * # noqa diff --git a/core/apps/api/filters/category.py b/core/apps/api/filters/category.py new file mode 100644 index 0000000..97d8b4a --- /dev/null +++ b/core/apps/api/filters/category.py @@ -0,0 +1,14 @@ +from django_filters import rest_framework as filters + +from core.apps.api.models import Category + + +class CategoryFilter(filters.FilterSet): + # name = filters.CharFilter(field_name="name", lookup_expr="icontains") + + class Meta: + model = Category + fields = [ + "show_home", + "id", + ] diff --git a/core/apps/api/migrations/0003_category_image.py b/core/apps/api/migrations/0003_category_image.py new file mode 100644 index 0000000..1d24a56 --- /dev/null +++ b/core/apps/api/migrations/0003_category_image.py @@ -0,0 +1,18 @@ +# 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'), + ), + ] diff --git a/core/apps/api/models/ad/category.py b/core/apps/api/models/ad/category.py index 5b6e560..4ee6afa 100644 --- a/core/apps/api/models/ad/category.py +++ b/core/apps/api/models/ad/category.py @@ -8,6 +8,14 @@ class Category(AbstractBaseModel): parent = models.ForeignKey('self', null=True, blank=True, related_name='children', on_delete=models.CASCADE) show_home = models.BooleanField(default=False, verbose_name=_('Show Home')) level = models.IntegerField(default=0, verbose_name=_('Level')) + image = models.ImageField(verbose_name=_('Image'), null=True, blank=True) + + 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/serializers/__init__.py b/core/apps/api/serializers/__init__.py index e69de29..d63c50f 100644 --- a/core/apps/api/serializers/__init__.py +++ b/core/apps/api/serializers/__init__.py @@ -0,0 +1 @@ +from .category import * # noqa diff --git a/core/apps/api/serializers/category/__init__.py b/core/apps/api/serializers/category/__init__.py new file mode 100644 index 0000000..d63c50f --- /dev/null +++ b/core/apps/api/serializers/category/__init__.py @@ -0,0 +1 @@ +from .category import * # noqa diff --git a/core/apps/api/serializers/category/category.py b/core/apps/api/serializers/category/category.py new file mode 100644 index 0000000..9436fe8 --- /dev/null +++ b/core/apps/api/serializers/category/category.py @@ -0,0 +1,44 @@ +from rest_framework import serializers + +from core.apps.api.models import Category + + +class BaseCategorySerializer(serializers.ModelSerializer): + children = serializers.SerializerMethodField() + + class Meta: + model = Category + fields = [ + "id", + "name", + "show_home", + "level", + "children", + ] + + def get_children(self, obj): + qs = obj.children.all() + return BaseCategorySerializer(qs, many=True, context=self.context).data + + +class ListCategorySerializer(BaseCategorySerializer): + class Meta(BaseCategorySerializer.Meta): ... + + +class ListCategoryNoChildSerializer(BaseCategorySerializer): + class Meta(BaseCategorySerializer.Meta): + fields = [ + "id", + "name", + "show_home", + "level", + "image", + ] + + +class RetrieveCategorySerializer(BaseCategorySerializer): + class Meta(BaseCategorySerializer.Meta): ... + + +class CreateCategorySerializer(BaseCategorySerializer): + class Meta(BaseCategorySerializer.Meta): ... diff --git a/core/apps/api/urls.py b/core/apps/api/urls.py index 5fa41be..bbbe74f 100644 --- a/core/apps/api/urls.py +++ b/core/apps/api/urls.py @@ -1,9 +1,8 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter +from core.apps.api.views import CategoryViewSet + router = DefaultRouter() - - -urlpatterns = [ - path("", include(router.urls)), -] +router.register("category", CategoryViewSet, basename="category") +urlpatterns = [path("", include(router.urls))] diff --git a/core/apps/api/views/__init__.py b/core/apps/api/views/__init__.py index e69de29..d63c50f 100644 --- a/core/apps/api/views/__init__.py +++ b/core/apps/api/views/__init__.py @@ -0,0 +1 @@ +from .category import * # noqa diff --git a/core/apps/api/views/category/__init__.py b/core/apps/api/views/category/__init__.py new file mode 100644 index 0000000..d63c50f --- /dev/null +++ b/core/apps/api/views/category/__init__.py @@ -0,0 +1 @@ +from .category import * # noqa diff --git a/core/apps/api/views/category/category.py b/core/apps/api/views/category/category.py new file mode 100644 index 0000000..ea08a04 --- /dev/null +++ b/core/apps/api/views/category/category.py @@ -0,0 +1,45 @@ +from rest_framework.permissions import AllowAny +from rest_framework.viewsets import ReadOnlyModelViewSet +from django_core.mixins.base import BaseViewSetMixin +from drf_spectacular.utils import extend_schema +from core.apps.api.models import Category +from django_filters.rest_framework import DjangoFilterBackend +from core.apps.api.filters.category import CategoryFilter +from core.apps.api.serializers.category import ( + ListCategorySerializer, + RetrieveCategorySerializer, + CreateCategorySerializer, + ListCategoryNoChildSerializer, +) + + +@extend_schema(tags=["Category"]) +class CategoryViewSet(BaseViewSetMixin, ReadOnlyModelViewSet): + permission_classes = [AllowAny] + serializer_class = ListCategorySerializer + pagination_class = None + filter_backends = [DjangoFilterBackend] + filterset_class = CategoryFilter + + action_permission_classes = {} + action_serializer_class = { + "list": ListCategorySerializer, + "retrieve": RetrieveCategorySerializer, + "create": CreateCategorySerializer, + } + + def get_queryset(self): + qs = Category.objects.all() + + if not self.request.query_params: + qs = qs.filter(level=0) + return qs + + def get_serializer_class(self): + if "show_home" in self.request.query_params: + return ListCategoryNoChildSerializer + + if hasattr(self, 'action_serializer_class'): + return self.action_serializer_class.get(self.action, self.serializer_class) + + return super().get_serializer_class() diff --git a/core/apps/shared/urls.py b/core/apps/shared/urls.py index bc256db..3b67489 100644 --- a/core/apps/shared/urls.py +++ b/core/apps/shared/urls.py @@ -1,11 +1,8 @@ -from django.urls import path, include +from django.urls import include, path from rest_framework.routers import DefaultRouter + from .views import SettingsView router = DefaultRouter() router.register("settings", SettingsView, basename="settings") - - -urlpatterns = [ - path("", include(router.urls)), -] +urlpatterns = [path("", include(router.urls))] diff --git a/test.py b/test.py new file mode 100644 index 0000000..8402e19 --- /dev/null +++ b/test.py @@ -0,0 +1,41 @@ +import random +from core.apps.api.models import Category + + +def generate_categories(total=150): + Category.objects.all().delete() + + created = [] + queue = [] + + # 1) Root level: 3–5 ta + root_count = random.randint(3, 5) + for i in range(root_count): + cat = Category.objects.create(name=f"Category {len(created) + 1}") + created.append(cat) + queue.append(cat) + + # 2) Qolganlarini yaratamiz + while len(created) < total: + if not queue: + break + + parent = queue.pop(0) + + # Har bir parentga 1–3 ta bola + children_count = random.randint(1, 3) + + for _ in range(children_count): + if len(created) >= total: + break + + child = Category.objects.create( + name=f"Category {len(created) + 1}", + parent=parent, + ) + created.append(child) + + # bola yana parent bo'lishi mumkin — shuning uchun queue ga qo‘shamiz + queue.append(child) + + return f"{len(created)} ta category yaratildi!" From a7efc16cda5dc16ff9ed1c46a8b159801a5f5a34 Mon Sep 17 00:00:00 2001 From: muhammadvadud Date: Tue, 25 Nov 2025 12:16:23 +0500 Subject: [PATCH 2/3] Category modeliga o'zgartirish kiritildi --- core/apps/api/filters/category.py | 1 + .../migrations/0004_category_category_type.py | 18 ++++++++++++++++++ core/apps/api/models/ad/category.py | 3 +++ core/apps/api/serializers/category/category.py | 2 ++ 4 files changed, 24 insertions(+) create mode 100644 core/apps/api/migrations/0004_category_category_type.py diff --git a/core/apps/api/filters/category.py b/core/apps/api/filters/category.py index 97d8b4a..732c333 100644 --- a/core/apps/api/filters/category.py +++ b/core/apps/api/filters/category.py @@ -11,4 +11,5 @@ class CategoryFilter(filters.FilterSet): fields = [ "show_home", "id", + "category_type", ] diff --git a/core/apps/api/migrations/0004_category_category_type.py b/core/apps/api/migrations/0004_category_category_type.py new file mode 100644 index 0000000..1bb0995 --- /dev/null +++ b/core/apps/api/migrations/0004_category_category_type.py @@ -0,0 +1,18 @@ +# 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'), + ), + ] diff --git a/core/apps/api/models/ad/category.py b/core/apps/api/models/ad/category.py index 4ee6afa..224d22d 100644 --- a/core/apps/api/models/ad/category.py +++ b/core/apps/api/models/ad/category.py @@ -1,6 +1,7 @@ from django.db import models from django_core.models.base import AbstractBaseModel from django.utils.translation import gettext_lazy as _ +from core.apps.api.choices import AdCategoryType class Category(AbstractBaseModel): @@ -9,6 +10,8 @@ class Category(AbstractBaseModel): show_home = models.BooleanField(default=False, verbose_name=_('Show Home')) level = models.IntegerField(default=0, verbose_name=_('Level')) image = models.ImageField(verbose_name=_('Image'), null=True, blank=True) + category_type = models.CharField(max_length=255, verbose_name=_('Category Type'), choices=AdCategoryType, + default=AdCategoryType.PRODUCT) def save(self, *args, **kwargs): if self.parent: diff --git a/core/apps/api/serializers/category/category.py b/core/apps/api/serializers/category/category.py index 9436fe8..0e652c4 100644 --- a/core/apps/api/serializers/category/category.py +++ b/core/apps/api/serializers/category/category.py @@ -13,6 +13,7 @@ class BaseCategorySerializer(serializers.ModelSerializer): "name", "show_home", "level", + "category_type", "children", ] @@ -33,6 +34,7 @@ class ListCategoryNoChildSerializer(BaseCategorySerializer): "show_home", "level", "image", + "category_type", ] From 638438b62ab99f4c178a533f808cfbfc11317701 Mon Sep 17 00:00:00 2001 From: muhammadvadud Date: Tue, 25 Nov 2025 12:45:13 +0500 Subject: [PATCH 3/3] Search api lari tayyor --- core/apps/accounts/admin/__init__.py | 1 + core/apps/accounts/admin/others.py | 12 ++++++++ core/apps/api/permissions/__init__.py | 0 core/apps/api/serializers/__init__.py | 1 + core/apps/api/serializers/search/__init__.py | 1 + core/apps/api/serializers/search/search.py | 27 ++++++++++++++++++ core/apps/api/urls.py | 3 +- core/apps/api/views/__init__.py | 1 + core/apps/api/views/search/__init__.py | 1 + core/apps/api/views/search/search.py | 29 ++++++++++++++++++++ 10 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 core/apps/accounts/admin/others.py create mode 100644 core/apps/api/permissions/__init__.py create mode 100644 core/apps/api/serializers/search/__init__.py create mode 100644 core/apps/api/serializers/search/search.py create mode 100644 core/apps/api/views/search/__init__.py create mode 100644 core/apps/api/views/search/search.py diff --git a/core/apps/accounts/admin/__init__.py b/core/apps/accounts/admin/__init__.py index 6e3a821..a57453a 100644 --- a/core/apps/accounts/admin/__init__.py +++ b/core/apps/accounts/admin/__init__.py @@ -1,2 +1,3 @@ from .core import * # noqa from .user import * # noqa +from .others import * # noqa diff --git a/core/apps/accounts/admin/others.py b/core/apps/accounts/admin/others.py new file mode 100644 index 0000000..1be9174 --- /dev/null +++ b/core/apps/accounts/admin/others.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from unfold.admin import ModelAdmin + +from core.apps.accounts.models import SearchHistory + + +@admin.register(SearchHistory) +class SearchHistoryAdmin(ModelAdmin): + list_display = ( + "id", + "__str__", + ) diff --git a/core/apps/api/permissions/__init__.py b/core/apps/api/permissions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/api/serializers/__init__.py b/core/apps/api/serializers/__init__.py index d63c50f..5da6a0a 100644 --- a/core/apps/api/serializers/__init__.py +++ b/core/apps/api/serializers/__init__.py @@ -1 +1,2 @@ from .category import * # noqa +from .search import * # noqa diff --git a/core/apps/api/serializers/search/__init__.py b/core/apps/api/serializers/search/__init__.py new file mode 100644 index 0000000..afeb2e5 --- /dev/null +++ b/core/apps/api/serializers/search/__init__.py @@ -0,0 +1 @@ +from .search import * # noqa diff --git a/core/apps/api/serializers/search/search.py b/core/apps/api/serializers/search/search.py new file mode 100644 index 0000000..f815e7f --- /dev/null +++ b/core/apps/api/serializers/search/search.py @@ -0,0 +1,27 @@ +from rest_framework import serializers +from core.apps.accounts.models import SearchHistory + + +class BaseSearchHistorySerializer(serializers.ModelSerializer): + class Meta: + model = SearchHistory + fields = [ + "value", + ] + + +class ListSearchHistorySerializer(BaseSearchHistorySerializer): + class Meta(BaseSearchHistorySerializer.Meta): ... + + +class RetrieveSearchHistorySerializer(BaseSearchHistorySerializer): + class Meta(BaseSearchHistorySerializer.Meta): ... + + +class CreateSearchHistorySerializer(BaseSearchHistorySerializer): + class Meta(BaseSearchHistorySerializer.Meta): ... + + def create(self, validated_data): + validated_data['user'] = self.context['request'].user + history = SearchHistory.objects.create(**validated_data) + return history diff --git a/core/apps/api/urls.py b/core/apps/api/urls.py index bbbe74f..76446df 100644 --- a/core/apps/api/urls.py +++ b/core/apps/api/urls.py @@ -1,8 +1,9 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from core.apps.api.views import CategoryViewSet +from core.apps.api.views import CategoryViewSet, SearchHistoryViewSet router = DefaultRouter() router.register("category", CategoryViewSet, basename="category") +router.register("search-history", SearchHistoryViewSet, basename="search-history") urlpatterns = [path("", include(router.urls))] diff --git a/core/apps/api/views/__init__.py b/core/apps/api/views/__init__.py index d63c50f..5da6a0a 100644 --- a/core/apps/api/views/__init__.py +++ b/core/apps/api/views/__init__.py @@ -1 +1,2 @@ from .category import * # noqa +from .search import * # noqa diff --git a/core/apps/api/views/search/__init__.py b/core/apps/api/views/search/__init__.py new file mode 100644 index 0000000..afeb2e5 --- /dev/null +++ b/core/apps/api/views/search/__init__.py @@ -0,0 +1 @@ +from .search import * # noqa diff --git a/core/apps/api/views/search/search.py b/core/apps/api/views/search/search.py new file mode 100644 index 0000000..8ac3d2f --- /dev/null +++ b/core/apps/api/views/search/search.py @@ -0,0 +1,29 @@ +from rest_framework import mixins +from rest_framework.viewsets import GenericViewSet +from django_core.mixins.base import BaseViewSetMixin +from drf_spectacular.utils import extend_schema +from rest_framework.permissions import IsAuthenticated +from core.apps.accounts.models import SearchHistory +from core.apps.api.serializers.search import ( + ListSearchHistorySerializer, + RetrieveSearchHistorySerializer, + CreateSearchHistorySerializer, + +) + + +@extend_schema(tags=['Search']) +class SearchHistoryViewSet(BaseViewSetMixin, mixins.ListModelMixin, mixins.CreateModelMixin, + mixins.DestroyModelMixin, GenericViewSet): + serializer_class = ListSearchHistorySerializer + permission_classes = [IsAuthenticated] + http_method_names = ['get', 'post', 'delete'] + action_permission_classes = {} + action_serializer_class = { + 'list': ListSearchHistorySerializer, + 'create': CreateSearchHistorySerializer, + } + + def get_queryset(self): + queryset = SearchHistory.objects.filter(user=self.request.user) + return queryset