From 97e7098b9ee0e51b31357857d463dbc453c6f769 Mon Sep 17 00:00:00 2001 From: muhammadvadud Date: Fri, 28 Nov 2025 16:49:38 +0500 Subject: [PATCH] Ad uchun api chiqarildi --- core/apps/accounts/admin/others.py | 10 +- core/apps/accounts/admin/user.py | 2 + .../accounts/migrations/0008_user_avatar.py | 18 ++ .../migrations/0009_alter_user_avatar.py | 18 ++ .../migrations/0010_alter_business_user.py | 20 ++ core/apps/accounts/models/business.py | 3 +- core/apps/accounts/models/user.py | 2 +- core/apps/api/admin/ad_items/__init__.py | 1 + core/apps/api/admin/ad_items/ad_options.py | 12 + core/apps/api/filters/__init__.py | 1 + core/apps/api/filters/ad.py | 85 ++++++ .../migrations/0014_admodel_description.py | 19 ++ .../api/migrations/0015_alter_adoption_ad.py | 19 ++ core/apps/api/models/ad/ad.py | 1 + core/apps/api/models/ad_items/ad_option.py | 2 +- core/apps/api/serializers/ad/__init__.py | 1 + core/apps/api/serializers/ad/ad.py | 270 ++++++++++++++++++ core/apps/api/serializers/ad/home_api.py | 14 +- core/apps/api/tests/ad/__init__.py | 1 + core/apps/api/tests/ad/test_ad.py | 58 ++++ core/apps/api/urls.py | 3 +- core/apps/api/views/ad/__init__.py | 1 + core/apps/api/views/ad/ad.py | 43 +++ resources/media/.gitignore | 3 +- 24 files changed, 600 insertions(+), 7 deletions(-) create mode 100644 core/apps/accounts/migrations/0008_user_avatar.py create mode 100644 core/apps/accounts/migrations/0009_alter_user_avatar.py create mode 100644 core/apps/accounts/migrations/0010_alter_business_user.py create mode 100644 core/apps/api/admin/ad_items/ad_options.py create mode 100644 core/apps/api/filters/ad.py create mode 100644 core/apps/api/migrations/0014_admodel_description.py create mode 100644 core/apps/api/migrations/0015_alter_adoption_ad.py create mode 100644 core/apps/api/serializers/ad/ad.py create mode 100644 core/apps/api/tests/ad/test_ad.py create mode 100644 core/apps/api/views/ad/ad.py diff --git a/core/apps/accounts/admin/others.py b/core/apps/accounts/admin/others.py index e2821b5..249fe50 100644 --- a/core/apps/accounts/admin/others.py +++ b/core/apps/accounts/admin/others.py @@ -1,7 +1,7 @@ from django.contrib import admin from unfold.admin import ModelAdmin -from core.apps.accounts.models import SearchHistory, UserLike, UserNotification, Notification +from core.apps.accounts.models import SearchHistory, UserLike, UserNotification, Notification, Business @admin.register(SearchHistory) @@ -34,3 +34,11 @@ class NotificationAdmin(ModelAdmin): "id", "__str__", ) + + +@admin.register(Business) +class BusinessAdmin(ModelAdmin): + list_display = ( + "id", + "__str__", + ) diff --git a/core/apps/accounts/admin/user.py b/core/apps/accounts/admin/user.py index 5da1088..78afd05 100644 --- a/core/apps/accounts/admin/user.py +++ b/core/apps/accounts/admin/user.py @@ -30,6 +30,8 @@ class CustomUserAdmin(admin.UserAdmin, ModelAdmin): "user_permissions", "role", "validated_at", + "account_type", + "avatar", ), }, ), diff --git a/core/apps/accounts/migrations/0008_user_avatar.py b/core/apps/accounts/migrations/0008_user_avatar.py new file mode 100644 index 0000000..1d15a32 --- /dev/null +++ b/core/apps/accounts/migrations/0008_user_avatar.py @@ -0,0 +1,18 @@ +# 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'), + ), + ] diff --git a/core/apps/accounts/migrations/0009_alter_user_avatar.py b/core/apps/accounts/migrations/0009_alter_user_avatar.py new file mode 100644 index 0000000..fdc457c --- /dev/null +++ b/core/apps/accounts/migrations/0009_alter_user_avatar.py @@ -0,0 +1,18 @@ +# 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'), + ), + ] diff --git a/core/apps/accounts/migrations/0010_alter_business_user.py b/core/apps/accounts/migrations/0010_alter_business_user.py new file mode 100644 index 0000000..031ecd7 --- /dev/null +++ b/core/apps/accounts/migrations/0010_alter_business_user.py @@ -0,0 +1,20 @@ +# 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'), + ), + ] diff --git a/core/apps/accounts/models/business.py b/core/apps/accounts/models/business.py index 1547a96..079699c 100644 --- a/core/apps/accounts/models/business.py +++ b/core/apps/accounts/models/business.py @@ -6,7 +6,8 @@ from django.contrib.auth import get_user_model class Business(AbstractBaseModel): name = models.CharField(max_length=255, verbose_name=_('Business Name')) - user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE) + user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name="business", + verbose_name=_('User')) 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')) diff --git a/core/apps/accounts/models/user.py b/core/apps/accounts/models/user.py index 44285f4..578a1a1 100644 --- a/core/apps/accounts/models/user.py +++ b/core/apps/accounts/models/user.py @@ -19,7 +19,7 @@ class User(auth_models.AbstractUser): choices=RoleChoice, default=RoleChoice.USER, ) - + avatar = models.ImageField("Avatar", upload_to="avatars/", default="avatars/default.png") USERNAME_FIELD = "phone" objects = UserManager() diff --git a/core/apps/api/admin/ad_items/__init__.py b/core/apps/api/admin/ad_items/__init__.py index 6598a6f..b16fc74 100644 --- a/core/apps/api/admin/ad_items/__init__.py +++ b/core/apps/api/admin/ad_items/__init__.py @@ -2,3 +2,4 @@ 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 diff --git a/core/apps/api/admin/ad_items/ad_options.py b/core/apps/api/admin/ad_items/ad_options.py new file mode 100644 index 0000000..93c5436 --- /dev/null +++ b/core/apps/api/admin/ad_items/ad_options.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from unfold.admin import ModelAdmin + +from core.apps.api.models import AdOption + + +@admin.register(AdOption) +class AdOptionAdmin(ModelAdmin): + list_display = ( + "id", + "__str__", + ) diff --git a/core/apps/api/filters/__init__.py b/core/apps/api/filters/__init__.py index d63c50f..121b071 100644 --- a/core/apps/api/filters/__init__.py +++ b/core/apps/api/filters/__init__.py @@ -1 +1,2 @@ from .category import * # noqa +from .ad import * # noqa diff --git a/core/apps/api/filters/ad.py b/core/apps/api/filters/ad.py new file mode 100644 index 0000000..6b8627e --- /dev/null +++ b/core/apps/api/filters/ad.py @@ -0,0 +1,85 @@ +from django_filters import rest_framework as filters +from django.db.models import ( + F, Case, When, FloatField, ExpressionWrapper, Subquery, OuterRef +) +from core.apps.api.models import AdVariant, AdModel + + +class AdFilter(filters.FilterSet): + min_price = filters.NumberFilter(field_name="real_price", lookup_expr="gte") + max_price = filters.NumberFilter(field_name="real_price", lookup_expr="lte") + + size = filters.CharFilter(method="filter_by_size") + color = filters.CharFilter(method="filter_by_color") + category = filters.CharFilter(field_name="category__name", lookup_expr="icontains") + created_at = filters.DateTimeFilter(field_name="created_at", lookup_expr="gte") + has_discount = filters.BooleanFilter(method="filter_has_discount") + has_normal_user = filters.BooleanFilter(method="filter_has_normal_user") + has_business_user = filters.BooleanFilter(method="filter_has_business_user") + + class Meta: + model = AdModel + fields = ["min_price", "max_price"] + + def filter_has_business_user(self, queryset, name, value): + return queryset.filter( + user__account_type="business" + ) + + def filter_has_normal_user(self, queryset, name, value): + return queryset.filter( + user__account_type="personal" + ) + + def filter_has_discount(self, queryset, name, value): + return queryset.filter( + variants__discount__gte=1 + ).distinct() + + def filter_by_size(self, queryset, name, value): + return queryset.filter( + variants__variant="Size", + variants__value__iexact=value + ).distinct() + + def filter_by_color(self, queryset, name, value): + return queryset.filter( + variants__variant="Color", + variants__value__iexact=value + ).distinct() + + def filter_queryset(self, queryset): + variant_real_price_expr = Case( + When(discount=-1, then=F("price")), + When( + discount__gte=0, + then=ExpressionWrapper( + F("price") - (F("price") * F("discount") / 100), + output_field=FloatField() + ) + ), + output_field=FloatField() + ) + + cheapest_variant_qs = ( + AdVariant.objects + .filter(ad=OuterRef("pk")) + .annotate(real_price=variant_real_price_expr) + .order_by("real_price") + .values("real_price")[:1] + ) + + ad_real_price = F("price") + + queryset = queryset.annotate( + real_price=Case( + When( + variants__isnull=False, + then=Subquery(cheapest_variant_qs) + ), + default=ad_real_price, + output_field=FloatField() + ) + ).distinct() + + return super().filter_queryset(queryset) diff --git a/core/apps/api/migrations/0014_admodel_description.py b/core/apps/api/migrations/0014_admodel_description.py new file mode 100644 index 0000000..b2ebbe7 --- /dev/null +++ b/core/apps/api/migrations/0014_admodel_description.py @@ -0,0 +1,19 @@ +# 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, + ), + ] diff --git a/core/apps/api/migrations/0015_alter_adoption_ad.py b/core/apps/api/migrations/0015_alter_adoption_ad.py new file mode 100644 index 0000000..00675db --- /dev/null +++ b/core/apps/api/migrations/0015_alter_adoption_ad.py @@ -0,0 +1,19 @@ +# 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'), + ), + ] diff --git a/core/apps/api/models/ad/ad.py b/core/apps/api/models/ad/ad.py index a682b4f..fb08480 100644 --- a/core/apps/api/models/ad/ad.py +++ b/core/apps/api/models/ad/ad.py @@ -18,6 +18,7 @@ class AdModel(AbstractBaseModel): plan = models.ForeignKey("api.AdTopPlan", on_delete=models.CASCADE, verbose_name=_("Plan")) tags = models.ManyToManyField("api.Tags", verbose_name=_("Tags")) image = models.ImageField(verbose_name=_("Image")) + description = models.TextField(verbose_name=_("Description")) @classmethod def _baker(cls): diff --git a/core/apps/api/models/ad_items/ad_option.py b/core/apps/api/models/ad_items/ad_option.py index 11d54e5..5aa449c 100644 --- a/core/apps/api/models/ad_items/ad_option.py +++ b/core/apps/api/models/ad_items/ad_option.py @@ -7,7 +7,7 @@ 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) + ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, related_name="options", verbose_name=_("Ad")) def __str__(self): return str(self.pk) diff --git a/core/apps/api/serializers/ad/__init__.py b/core/apps/api/serializers/ad/__init__.py index 77f06da..61a687b 100644 --- a/core/apps/api/serializers/ad/__init__.py +++ b/core/apps/api/serializers/ad/__init__.py @@ -1 +1,2 @@ from .home_api import * # noqa +from .ad import * # noqa diff --git a/core/apps/api/serializers/ad/ad.py b/core/apps/api/serializers/ad/ad.py new file mode 100644 index 0000000..789f306 --- /dev/null +++ b/core/apps/api/serializers/ad/ad.py @@ -0,0 +1,270 @@ +from rest_framework import serializers +from django.db.models import Avg + +from core.apps.accounts.choices import AccountType +from core.apps.api.models import AdModel, AdVariant, Category, AdImage, AdOption +from core.apps.accounts.models import UserLike +from core.apps.api.choices import AdVariantType + + +class AdOptionSerializer(serializers.ModelSerializer): + class Meta: + model = AdOption + fields = [ + "id", + "name", + "value", + ] + + +class CategorySerializer(serializers.ModelSerializer): + class Meta: + model = Category + fields = ["id", "name"] + + +class AdImageSerializer(serializers.ModelSerializer): + class Meta: + model = AdImage + fields = [ + "image", + "ad_variant" + ] + + def to_representation(self, instance): + data = super().to_representation(instance) + + if instance.ad_variant is None: + data.pop("ad_variant", None) + + return data + + +class AdVariantSerializer(serializers.ModelSerializer): + class Meta: + model = AdVariant + fields = [ + "id", + "variant", + "value", + "is_available", + "price", + "discount", + ] + + +class BaseAdSerializer(serializers.ModelSerializer): + is_liked = serializers.SerializerMethodField() + star = serializers.SerializerMethodField() + comment_count = serializers.SerializerMethodField() + + class Meta: + model = AdModel + fields = [ + "id", + "name", + "price", + "image", + "is_liked", + "star", + "comment_count", + ] + + 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): + request = self.context.get("request") + user = getattr(request, "user", None) + + if not user or not user.is_authenticated: + return False + + return UserLike.objects.filter(user=user, ad=obj).exists() + + +class ListAdSerializer(BaseAdSerializer): + price = serializers.SerializerMethodField() + discount = serializers.SerializerMethodField() + + class Meta(BaseAdSerializer.Meta): + fields = [ + "id", + "name", + "price", + "image", + "star", + "comment_count", + "discount", + "is_liked", + ] + + def _get_first_variant(self, obj): + if not hasattr(self, "_variant_cache"): + self._variant_cache = {} + if obj.id not in self._variant_cache: + self._variant_cache[obj.id] = obj.variants.order_by("price").first() + return self._variant_cache[obj.id] + + def get_price(self, obj): + variant = self._get_first_variant(obj) + if not variant: + return obj.price + return variant.price if variant else 0 + + def get_discount(self, obj): + variant = self._get_first_variant(obj) + return variant.discount if variant else -1.0 + + +class FullListAdSerializer(serializers.Serializer): + ads = ListAdSerializer(many=True) + categories = serializers.SerializerMethodField() + colors = serializers.SerializerMethodField() + sizes = serializers.SerializerMethodField() + min_price = serializers.SerializerMethodField() + max_price = serializers.SerializerMethodField() + + def get_categories(self, obj): + ads = obj.get("ads", []) + + category_ids = set() + categories = [] + + for ad in ads: + category = ad.category + if category and category.id not in category_ids: + category_ids.add(category.id) + categories.append(category) + + return CategorySerializer(categories, many=True).data + + def get_colors(self, obj): + ads = obj.get("ads", []) + color_values = set() + + for ad in ads: + variants = getattr(ad, "variants", []) + for v in variants.all(): + if v.variant == AdVariantType.COLOR: + color_values.add(v.value) + + return list(color_values) + + def get_sizes(self, obj): + ads = obj.get("ads", []) + size_values = set() + + for ad in ads: + variants = getattr(ad, "variants", []) + for v in variants.all(): + if v.variant == AdVariantType.SIZE: + size_values.add(v.value) + + return list(size_values) + + def get_min_price(self, obj): + ads = obj.get("ads", []) + prices = [] + + for ad in ads: + ad_data = ListAdSerializer(ad, context=self.context).data + price = ad_data.get("price") + if price is not None: + prices.append(price) + + return min(prices) if prices else None + + def get_max_price(self, obj): + ads = obj.get("ads", []) + prices = [] + + for ad in ads: + ad_data = ListAdSerializer(ad, context=self.context).data + price = ad_data.get("price") + if price is not None: + prices.append(price) + + return max(prices) if prices else None + + +class RetrieveAdSerializer(BaseAdSerializer): + variants = AdVariantSerializer(many=True, read_only=True) + images = serializers.SerializerMethodField() + colors = serializers.SerializerMethodField() + sizes = serializers.SerializerMethodField() + creator = serializers.SerializerMethodField() + options = AdOptionSerializer(many=True, read_only=True) + + class Meta(BaseAdSerializer.Meta): + fields = [ + "id", + "name", + "price", + "image", + "star", + "comment_count", + "is_liked", + "images", + "variants", + "colors", + "sizes", + "creator", + "description", + "options" + ] + + def get_images(self, obj): + objects = obj.images.all() + return AdImageSerializer(objects, many=True, context=self.context).data + + def get_colors(self, obj): + color_values = set() + + variants = getattr(obj, "variants", []) + for v in variants.all(): + if v.variant == AdVariantType.COLOR: + color_values.add(v.value) + + return list(color_values) + + def get_sizes(self, obj): + size_values = set() + + variants = getattr(obj, "variants", []) + for v in variants.all(): + if v.variant == AdVariantType.SIZE: + size_values.add(v.value) + return list(size_values) + + def get_creator(self, obj): + user = obj.user + user_type = user.account_type + request = self.context.get("request") + + avatar_url = request.build_absolute_uri(user.avatar.url) if user.avatar else None + + if user_type == AccountType.BUSINESS: + return { + "username": user.business.name, + "avatar": avatar_url, + "create_at": user.validated_at, + "last_live": "endi qo'shamiz! waiting pls ))" + } + else: + username = f"{user.first_name} {user.last_name}" + return { + "username": username, + "avatar": avatar_url, + "create_at": user.validated_at, + "last_live": "endi qo'shamiz! waiting pls ))" + } + + +class CreateAdSerializer(BaseAdSerializer): + class Meta(BaseAdSerializer.Meta): ... diff --git a/core/apps/api/serializers/ad/home_api.py b/core/apps/api/serializers/ad/home_api.py index d709984..3d10951 100644 --- a/core/apps/api/serializers/ad/home_api.py +++ b/core/apps/api/serializers/ad/home_api.py @@ -1,6 +1,7 @@ from rest_framework import serializers from django.db.models import Avg from core.apps.api.models import AdModel, AdVariant +from core.apps.accounts.models import UserLike class AdVariantSerializer(serializers.ModelSerializer): @@ -20,6 +21,7 @@ class BaseHomeAdSerializer(serializers.ModelSerializer): comment_count = serializers.SerializerMethodField() price = serializers.SerializerMethodField() discount = serializers.SerializerMethodField() + is_liked = serializers.SerializerMethodField() class Meta: model = AdModel @@ -31,6 +33,7 @@ class BaseHomeAdSerializer(serializers.ModelSerializer): "star", "comment_count", "discount", + "is_liked", ] def _get_first_variant(self, obj): @@ -48,7 +51,7 @@ class BaseHomeAdSerializer(serializers.ModelSerializer): def get_discount(self, obj): variant = self._get_first_variant(obj) - return variant.discount if variant else 0 + return variant.discount if variant else -1.0 def get_star(self, obj): avg = obj.feedback.aggregate(avg=Avg("star"))["avg"] @@ -58,6 +61,15 @@ class BaseHomeAdSerializer(serializers.ModelSerializer): count = obj.feedback.count() return count or 0 + def get_is_liked(self, obj): + request = self.context.get("request") + user = getattr(request, "user", None) + + if not user or not user.is_authenticated: + return False + + return UserLike.objects.filter(user=user, ad=obj).exists() + class ListHomeAdSerializer(BaseHomeAdSerializer): class Meta(BaseHomeAdSerializer.Meta): ... diff --git a/core/apps/api/tests/ad/__init__.py b/core/apps/api/tests/ad/__init__.py index 2bcb0e0..49efb0e 100644 --- a/core/apps/api/tests/ad/__init__.py +++ b/core/apps/api/tests/ad/__init__.py @@ -1 +1,2 @@ from .test_home_api import * # noqa +from .test_ad import * # noqa diff --git a/core/apps/api/tests/ad/test_ad.py b/core/apps/api/tests/ad/test_ad.py new file mode 100644 index 0000000..7725462 --- /dev/null +++ b/core/apps/api/tests/ad/test_ad.py @@ -0,0 +1,58 @@ +import pytest +from django.urls import reverse +from rest_framework.test import APIClient + +from core.apps.api.models import AdModel + + +@pytest.fixture +def instance(db): + return AdModel._baker() + + +@pytest.fixture +def api_client(instance): + client = APIClient() + ##client.force_authenticate(user=instance.user) + return client, instance + + +@pytest.fixture +def data(api_client): + client, instance = api_client + return ( + { + "list": reverse("ads-list"), + "retrieve": reverse("ads-detail", kwargs={"pk": instance.pk}), + "retrieve-not-found": reverse("ads-detail", kwargs={"pk": 1000}), + }, + client, + instance, + ) + + +@pytest.mark.django_db +def test_list(data): + urls, client, _ = data + response = client.get(urls["list"]) + data_resp = response.json() + assert response.status_code == 200 + assert data_resp["status"] is True + + +@pytest.mark.django_db +def test_retrieve(data): + urls, client, _ = data + response = client.get(urls["retrieve"]) + data_resp = response.json() + assert response.status_code == 200 + assert data_resp["status"] is True + + +@pytest.mark.django_db +def test_retrieve_not_found(data): + urls, client, _ = data + response = client.get(urls["retrieve-not-found"]) + data_resp = response.json() + assert response.status_code == 404 + assert data_resp["status"] is False diff --git a/core/apps/api/urls.py b/core/apps/api/urls.py index f07350d..9e69b11 100644 --- a/core/apps/api/urls.py +++ b/core/apps/api/urls.py @@ -2,9 +2,10 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter from core.apps.api.views import CategoryHomeApiViewSet, CategoryViewSet, HomeAdApiView, SearchHistoryViewSet, \ - UserLikeViewSet, NotificationViewSet, BannerViewSet + UserLikeViewSet, NotificationViewSet, BannerViewSet, AdsView router = DefaultRouter() +router.register("ads", AdsView, basename="ads") router.register("banner", BannerViewSet, basename="banner") router.register("notification", NotificationViewSet, basename="notification") router.register("user-like", UserLikeViewSet, basename="user-like") diff --git a/core/apps/api/views/ad/__init__.py b/core/apps/api/views/ad/__init__.py index 77f06da..61a687b 100644 --- a/core/apps/api/views/ad/__init__.py +++ b/core/apps/api/views/ad/__init__.py @@ -1 +1,2 @@ from .home_api import * # noqa +from .ad import * # noqa diff --git a/core/apps/api/views/ad/ad.py b/core/apps/api/views/ad/ad.py new file mode 100644 index 0000000..833bc57 --- /dev/null +++ b/core/apps/api/views/ad/ad.py @@ -0,0 +1,43 @@ +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 django_filters.rest_framework import DjangoFilterBackend +from core.apps.api.filters import AdFilter +from core.apps.api.serializers.ad.ad import ( + FullListAdSerializer, + RetrieveAdSerializer, + CreateAdSerializer, +) + + +@extend_schema(tags=["Ads"]) +class AdsView(BaseViewSetMixin, ReadOnlyModelViewSet): + queryset = AdModel.objects.all().order_by("-created_at") + serializer_class = FullListAdSerializer + permission_classes = [AllowAny] + filter_backends = [DjangoFilterBackend] + filterset_class = AdFilter + + action_permission_classes = {} + action_serializer_class = { + "list": FullListAdSerializer, + "retrieve": RetrieveAdSerializer, + "create": CreateAdSerializer, + } + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + page = self.paginate_queryset(queryset) + + if page is not None: + data = {"ads": page} + serializer = FullListAdSerializer(data, context={"request": request}) + return self.get_paginated_response(serializer.data) + + data = {"ads": queryset} + serializer = FullListAdSerializer(data, context={"request": request}) + response = self.get_paginated_response(serializer.data) + + return response diff --git a/resources/media/.gitignore b/resources/media/.gitignore index c96a04f..362dabd 100644 --- a/resources/media/.gitignore +++ b/resources/media/.gitignore @@ -1,2 +1,3 @@ * -!.gitignore \ No newline at end of file +!.gitignore +!avatars/default.png \ No newline at end of file -- 2.49.1