new route

This commit is contained in:
Parsa Nazer
2025-05-19 20:41:54 +03:30
parent 3489a747e8
commit 2f1236a83d
14 changed files with 294 additions and 14 deletions
+7 -8
View File
@@ -160,8 +160,12 @@ UNFOLD = {
"title": _("زیر دسته بندی"),
"icon": "category",
"link": reverse_lazy("admin:product_subcategorymodel_changelist"),
}
},
{
"title": _("دسته بندی پورسانتی"),
"icon": "devices",
"link": reverse_lazy("admin:home_showcaseslider_changelist"),
},
],
},
{
@@ -186,12 +190,7 @@ UNFOLD = {
"icon": "newsmode",
"link": reverse_lazy("admin:blog_blogmodel_changelist"),
},
{
"title": _("نمایش کیس ها"),
"icon": "devices",
"link": reverse_lazy("admin:home_showcaseslider_changelist"),
},
],
},
@@ -0,0 +1,21 @@
# Generated by Django 5.1.2 on 2025-05-18 10:45
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('home', '0014_homeimagemodel_video1_homeimagemodel_video2_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='slidermodel',
options={'verbose_name': 'دسته بندی پورسانت', 'verbose_name_plural': 'دسته بندی پورسانت'},
),
migrations.RemoveField(
model_name='slidermodel',
name='link',
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.1.2 on 2025-05-18 11:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0015_alter_slidermodel_options_remove_slidermodel_link'),
]
operations = [
migrations.AlterModelOptions(
name='slidermodel',
options={'verbose_name': 'اسلایدر', 'verbose_name_plural': 'اسلایدر ها'},
),
migrations.AddField(
model_name='slidermodel',
name='link',
field=models.URLField(default='', verbose_name='لینک'),
preserve_default=False,
),
]
@@ -0,0 +1,17 @@
# Generated by Django 5.1.2 on 2025-05-18 11:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('home', '0016_alter_slidermodel_options_slidermodel_link'),
]
operations = [
migrations.RemoveField(
model_name='showcaseslider',
name='link',
),
]
-2
View File
@@ -1,5 +1,4 @@
from django.db import models
from product.models import ProductModel
from django.urls import reverse
class SliderModel(models.Model):
@@ -42,7 +41,6 @@ class HomeImageModel(models.Model):
class ShowCaseSlider(models.Model):
title = models.CharField(max_length=30, verbose_name='عنوان')
description = models.CharField(max_length=150, verbose_name='توضیحات')
link = models.URLField(verbose_name='لینک')
image = models.ImageField(upload_to='show_case/', verbose_name='عکس')
def __str__(self):
return self.title
+1 -1
View File
@@ -153,7 +153,7 @@ class ProductVariantInLine(StackedInline):
readonly_fields = ['price']
# inlines = [DetailModelInLine]
autocomplete_fields = ['product_attributes', 'in_pack_items', 'images', 'details']
fields = ['images', 'video','input_price', 'min_price', 'currency', 'price', 'discount','in_stock', 'color', 'product_attributes', 'in_pack_items', 'details','sell']
fields = ['images', 'video','input_price', 'min_price', 'currency', 'price', 'discount','in_stock', 'color', 'product_attributes', 'in_pack_items', 'details', 'sell', 'slider_category']
# search_fields = ['']
@@ -0,0 +1,19 @@
# Generated by Django 5.1.2 on 2025-04-24 22:25
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0040_remove_detailmodel_name'),
]
operations = [
migrations.AlterField(
model_name='detailmodel',
name='detail_model',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='details', to='product.productdetailmodel', verbose_name='دسته بندی جزيات'),
),
]
@@ -0,0 +1,20 @@
# Generated by Django 5.1.2 on 2025-05-18 10:56
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0015_alter_slidermodel_options_remove_slidermodel_link'),
('product', '0041_alter_detailmodel_detail_model'),
]
operations = [
migrations.AddField(
model_name='productvariant',
name='porsant_category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='home.slidermodel', verbose_name='دسته بندی پورسانتی'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2025-05-18 11:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0042_productvariant_porsant_category'),
]
operations = [
migrations.RenameField(
model_name='productvariant',
old_name='porsant_category',
new_name='slider_category',
),
]
@@ -0,0 +1,20 @@
# Generated by Django 5.1.2 on 2025-05-18 11:47
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0017_remove_showcaseslider_link'),
('product', '0043_rename_porsant_category_productvariant_slider_category'),
]
operations = [
migrations.AlterField(
model_name='productvariant',
name='slider_category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='home.showcaseslider', verbose_name='دسته بندی پورسانتی'),
),
]
+2 -1
View File
@@ -5,7 +5,7 @@ from django.urls import reverse
import requests
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from home.models import ShowCaseSlider
class MainCategoryModel(models.Model):
name = models.CharField(max_length=50, verbose_name='نام دسته بندی')
slug = models.SlugField(max_length=50, unique=True, help_text="اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید")
@@ -243,6 +243,7 @@ class ProductVariant(models.Model):
images = models.ManyToManyField(ProductImageModel, verbose_name='عکس ها')
video = models.FileField(upload_to='product_videos/', blank=True, null=True, verbose_name='ویدیو')
details = models.ManyToManyField(ProductDetailModel, verbose_name='جزییات محصول', related_name='product')
slider_category = models.ForeignKey(ShowCaseSlider, verbose_name='دسته بندی پورسانتی', blank=True, null=True, on_delete=models.CASCADE)
class Meta:
verbose_name = 'تنوع محصول'
verbose_name_plural = 'تنوع‌های محصول'
+6 -1
View File
@@ -130,12 +130,17 @@ class DynamicProductSerializer(serializers.ModelSerializer):
fields = "__all__"
view_type = {
'list': ['id','name', 'rating', 'slug', 'category', 'variants', 'colors'],
'slider': ['id','name', 'rating', 'slug', 'category', 'variants', 'colors'],
'instance': ['id', 'name', 'description', 'rating', 'slug', 'meta_description', 'meta_keywords', 'meta_rating', 'category', 'related_products', 'in_pack_items', 'variants', 'colors'],
'chat': ['id', 'name', 'description', 'variants']
}
def get_variants(self, obj):
varients = obj.variants.all()
view_type = self.context.get('view_type')
if view_type == 'slider':
varients = obj.variants.filter(slider_category__isnull=False)
else:
varients = obj.variants.all()
colors = set(varient.color for varient in varients)
return ProductVariantSerialzier(instance=varients, many=True, context=self.context).data
+2 -1
View File
@@ -1,8 +1,9 @@
from django.urls import path
from .views import AllCategories, ProductView, AllProductsView, CommentView
from .views import AllCategories, ProductView, AllProductsView, CommentView, ShowCaseProductsView
urlpatterns = [
path('', AllProductsView.as_view(), name='category-products'),
path('slider_category', ShowCaseProductsView.as_view(), name='category-products'),
path('categories', AllCategories.as_view(), name='all-categories'),
path('<int:pk>', ProductView.as_view(), name='product-detail'),
path('comments/<int:pk>', CommentView.as_view(), name='comment-views'),
+138
View File
@@ -13,6 +13,7 @@ from rest_framework.permissions import AllowAny
from order.serializers import OrderItemSerailzier
from order.models import OrderModel
from django.db.models import Min, Max
from home.models import ShowCaseSlider
# class APIView(APIView):
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
@@ -205,6 +206,143 @@ class AllProductsView(APIView):
return Response({"detail": "Category not found."}, status=status.HTTP_404_NOT_FOUND)
class ShowCaseProductsView(APIView):
serializer_class = DynamicProductSerializer
pagination_class = StructurePagination
authentication_classes = []
@extend_schema(
parameters=[
OpenApiParameter(
name="search",
description="Search by product name or description.",
required=False,
type=OpenApiTypes.STR,
),
OpenApiParameter(
name="slider_category",
type=OpenApiTypes.INT,
required=False,
),
OpenApiParameter(
name="price_gte",
description="Filter products with price greater than or equal to this value.",
required=False,
type=OpenApiTypes.FLOAT,
),
OpenApiParameter(
name="price_lte",
description="Filter products with price less than or equal to this value.",
required=False,
type=OpenApiTypes.FLOAT,
),
OpenApiParameter(
name="sort",
description=(
"Sort results by one of the following fields:\n"
"`name`, `-name`, `price`, `-price`, `created_at`, `-created_at`."
"\nPrefix with `-` for descending order."
"remove the price form sorting templory "
),
required=False,
type=OpenApiTypes.STR,
),
OpenApiParameter(
name="limit",
description="Number of results to return per page (pagination).",
required=False,
type=OpenApiTypes.INT,
),
OpenApiParameter(
name="offset",
description="The starting position of the results (pagination).",
required=False,
type=OpenApiTypes.INT,
),
OpenApiParameter(
name="in_stock",
description="Filter products that are in stock (positive stock).",
required=False,
type=OpenApiTypes.BOOL,
),
OpenApiParameter(
name="has_discount",
description="Filter products that have a discount.",
required=False,
type=OpenApiTypes.BOOL,
)
],
description=(
"Retrieve products with optional filters and sorting. "
"Provide a list of category IDs to filter products by those categories and their subcategories."
),
responses={
200: DynamicProductSerializer(many=True, context={'view_type': 'list'}),
404: OpenApiTypes.OBJECT,
},
)
def get(self, request):
try:
category_id = request.query_params.get('slider_category', None)
if category_id:
try:
category_id = int(category_id)
except ValueError:
return Response({'detail': 'value error category id should be a number'}, status=status.HTTP_400_BAD_REQUEST)
slider_category = get_object_or_404(ShowCaseSlider, pk=category_id)
products = ProductModel.objects.filter(variants__slider_category=slider_category).distinct()
else:
products = ProductModel.objects.filter(variants__slider_category__isnull=False).distinct()
# Filter by stock status if `in_stock` is specified
in_stock = request.query_params.get('in_stock', "false") == 'true'
if in_stock:
products = products.filter(variants__in_stock__gt=0)
# Filter by discount if `has_discount` is specified
has_discount = request.query_params.get('has_discount', "false") == 'true'
if has_discount:
products = products.filter(variants__discount__gt=0)
# Search filter
search_query = request.query_params.get('search', None)
if search_query:
products = products.filter(Q(name__icontains=search_query) | Q(description__icontains=search_query))
# Price filters
price_gte = request.query_params.get('price_gte', None)
price_lte = request.query_params.get('price_lte', None)
products = products.annotate(min_price=Min('variants__price'), max_price=Max('variants__price'))
if price_gte:
products = products.filter(max_price__gte=price_gte)
if price_lte:
products = products.filter(min_price__lte=price_lte)
# Sorting
sort_by = request.query_params.get('sort', None)
if sort_by in ['name', '-name', 'created_at', '-created_at']:
products = products.order_by(sort_by)
else:
products = products.order_by('name')
# Pagination
paginator = self.pagination_class()
paginated_products = paginator.paginate_queryset(products, request)
serializer = self.serializer_class(paginated_products, many=True, context={'request': request, 'view_type': 'slider'})
return paginator.get_paginated_response(serializer.data)
except MainCategoryModel.DoesNotExist:
return Response({"detail": "Category not found."}, status=status.HTTP_404_NOT_FOUND)
class CommentView(APIView):
serializer_class = CommentSerializer
permission_classes = [IsAuthenticatedOrReadOnly]