a lot of things about category

This commit is contained in:
Parsa Nazer
2025-01-19 21:56:11 +03:30
parent f178754ba6
commit a02bd923c0
9 changed files with 216 additions and 124 deletions
+6 -1
View File
@@ -297,7 +297,12 @@ UNFOLD = {
{
"title": _("دسته بندی"),
"icon": "category",
"link": reverse_lazy("admin:product_categorymodel_changelist"),
"link": reverse_lazy("admin:product_maincategorymodel_changelist"),
},
{
"title": _("زیر دسته بندی"),
"icon": "category",
"link": reverse_lazy("admin:product_subcategorymodel_changelist"),
},
{
"title": _("نظرات"),
+5 -2
View File
@@ -7,10 +7,13 @@ from unfold.admin import ModelAdmin
class ProductModelAdmin(ModelAdmin):
pass
@admin.register(CategoryModel)
class CategoryModelAdmin(ModelAdmin):
@admin.register(MainCategoryModel)
class MainCategoryModelAdmin(ModelAdmin):
pass
@admin.register(SubCategoryModel)
class SubCategoryModelAdmin(ModelAdmin):
pass
@admin.register(CommentModel)
class CommentAdmin(ModelAdmin):
@@ -0,0 +1,49 @@
# Generated by Django 5.1.2 on 2025-01-19 17:02
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0012_productmodel_category'),
]
operations = [
migrations.CreateModel(
name='MainCategoryModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='نام')),
('slug', models.SlugField(help_text='اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید', unique=True)),
('icon', models.CharField(blank=True, max_length=100, null=True, verbose_name='آیکون')),
('meta_title', models.CharField(blank=True, help_text='عنوان متا برای SEO', max_length=60, null=True, verbose_name='عنوان متا')),
('meta_description', models.TextField(blank=True, help_text='توضیحات متا برای SEO', max_length=160, null=True, verbose_name='توضیحات متا')),
],
options={
'verbose_name': 'دسته\u200cبندی اصلی',
'verbose_name_plural': 'دسته\u200cبندی\u200cهااصلی',
},
),
migrations.CreateModel(
name='SubCategoryModel',
fields=[
('maincategorymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='product.maincategorymodel')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='product.maincategorymodel', verbose_name='دسته\u200cبندی والد')),
],
options={
'verbose_name': 'زیر دسته\u200cبندی',
'verbose_name_plural': 'زیر دسته\u200cبندی\u200cها',
},
bases=('product.maincategorymodel',),
),
migrations.AlterField(
model_name='productmodel',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='product.subcategorymodel'),
),
migrations.DeleteModel(
name='CategoryModel',
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2025-01-19 17:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0013_maincategorymodel_subcategorymodel_and_more'),
]
operations = [
migrations.AlterField(
model_name='maincategorymodel',
name='icon',
field=models.ImageField(blank=True, null=True, upload_to='category_model/', verbose_name='آیکون'),
),
]
@@ -0,0 +1,24 @@
# Generated by Django 5.1.2 on 2025-01-19 18:21
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0014_alter_maincategorymodel_icon'),
]
operations = [
migrations.AlterField(
model_name='productmodel',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='product.subcategorymodel'),
),
migrations.AlterField(
model_name='subcategorymodel',
name='parent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subcategorys', to='product.maincategorymodel', verbose_name='دسته\u200cبندی والد'),
),
]
+17 -43
View File
@@ -5,55 +5,29 @@ from django.urls import reverse
import requests
class CategoryModel(models.Model):
name = models.CharField(max_length=50, verbose_name='نام دسته‌بندی')
slug = models.SlugField(
max_length=50,
unique=True,
help_text="اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید"
)
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
related_name='children',
null=True,
blank=True,
verbose_name='دسته‌بندی والد'
)
icon = models.CharField(max_length=100, verbose_name='آیکون دسته‌بندی', blank=True, null=True)
meta_title = models.CharField(
max_length=60,
verbose_name="عنوان متا",
help_text="عنوان متا برای SEO",
blank=True,
null=True
)
meta_description = models.TextField(
max_length=160,
verbose_name="توضیحات متا",
help_text="توضیحات متا برای SEO",
blank=True,
null=True
)
class MainCategoryModel(models.Model):
name = models.CharField(max_length=50, verbose_name='نام')
slug = models.SlugField(max_length=50, unique=True, help_text="اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید")
icon = models.ImageField(upload_to='category_model/',verbose_name='آیکون', blank=True, null=True)
meta_title = models.CharField(max_length=60, verbose_name="عنوان متا", help_text="عنوان متا برای SEO", blank=True, null=True)
meta_description = models.TextField(max_length=160, verbose_name="توضیحات متا", help_text="توضیحات متا برای SEO", blank=True, null=True)
class Meta:
verbose_name = "دسته‌بندی"
verbose_name_plural = "دسته‌بندی‌ها"
ordering = ['parent__id', 'id'] # Optional: to order by hierarchy
verbose_name = "دسته‌بندی اصلی"
verbose_name_plural = "دسته‌بندی‌هااصلی"
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('category_detail', kwargs={'slug': self.slug})
# def get_absolute_url(self):
# return reverse('category_detail', kwargs={'slug': self.slug})
def get_breadcrumb(self):
breadcrumb = []
category = self
while category:
breadcrumb.append(category)
category = category.parent
return breadcrumb[::-1]
class SubCategoryModel(MainCategoryModel):
parent = models.ForeignKey(MainCategoryModel, on_delete=models.CASCADE, related_name='subcategorys', null=True, blank=True, verbose_name='دسته‌بندی والد')
class Meta:
verbose_name = "زیر دسته‌بندی"
verbose_name_plural = "زیر دسته‌بندی‌ها"
class DollorModel(models.Model):
price = models.FloatField(null=True, blank=True)
@@ -113,7 +87,7 @@ class ProductModel(models.Model):
meta_keywords = models.CharField(max_length=300, blank=True, null=True, help_text='این فیلد را حتما پر کنید')
meta_rating = models.FloatField(default=5, help_text='امتیاز محصول')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='زمان ثبت محصول')
category = models.ForeignKey(CategoryModel, blank=True, null=True, on_delete=models.SET_NULL)
category = models.ForeignKey(SubCategoryModel, blank=True, null=True, on_delete=models.SET_NULL, related_name='products')
def format_discount_price(self):
discount_price = int(self.price * (100 - self.discount) / 100)
formatted_num = "{:,.0f}".format(discount_price)
+12 -11
View File
@@ -36,16 +36,17 @@ class CommentSerializer(serializers.ModelSerializer):
fields = "__all__"
read_only_fields = ('show', 'product')
class CategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class SubCategorySerializer(serializers.ModelSerializer):
product_count = serializers.SerializerMethodField()
class Meta:
model = CategoryModel
fields = ['id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'parent', 'children']
model = SubCategoryModel
fields = ['id', 'name', 'slug','icon', 'meta_title', 'meta_description', 'product_count']
def get_product_count(self, obj):
return obj.products.count()
def get_children(self, obj):
children = obj.children.all()
if children.exists():
return CategorySerializer(children, many=True).data
return []
class MainCategorySerializer(serializers.ModelSerializer):
subcategorys = SubCategorySerializer(many=True)
class Meta:
model = MainCategoryModel
fields = ['id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'subcategorys']
+30 -28
View File
@@ -23,30 +23,30 @@ from rest_framework.permissions import AllowAny
class AllCategories(APIView):
serializer_class = CategorySerializer
serializer_class = MainCategorySerializer
authentication_classes = []
@extend_schema(
parameters=[
OpenApiParameter(
name="search",
description="Search by category name or description.",
required=False,
type=OpenApiTypes.STR,
)
],
# parameters=[
# OpenApiParameter(
# name="search",
# description="Search by category name or description.",
# required=False,
# type=OpenApiTypes.STR,
# )
# ],
responses={
200: CategorySerializer(many=True),
200: MainCategorySerializer(many=True),
404: OpenApiTypes.OBJECT,
},
)
def get(self, request):
search_query = request.query_params.get('search', None)
if search_query:
categories = CategoryModel.objects.filter(Q(name__icontains=search_query) | Q(slug__icontains=search_query))
else:
categories = CategoryModel.objects.all()
# search_query = request.query_params.get('search', None)
# if search_query:
# categories = MainCategoryModel.objects.filter(Q(name__icontains=search_query) | Q(slug__icontains=search_query))
# else:
categories = MainCategoryModel.objects.all()
categories_ser = self.serializer_class(instance=categories, many=True)
return Response({"categories": categories_ser.data}, status=status.HTTP_200_OK)
return Response(categories_ser.data, status=status.HTTP_200_OK)
class ProductView(APIView):
serializer_class = ProductSerializer
@@ -71,13 +71,18 @@ class AllProductsView(APIView):
required=False,
type=OpenApiTypes.STR,
),
# OpenApiParameter(
# name="category",
# type={'type': 'array', 'items': {'type': 'number'}},
# location=OpenApiParameter.QUERY,
# required=False,
# style='form',
# explode=False,
# ),
OpenApiParameter(
name="category",
type={'type': 'array', 'items': {'type': 'number'}},
location=OpenApiParameter.QUERY,
type=OpenApiTypes.INT,
required=False,
style='form',
explode=False,
),
OpenApiParameter(
name="price_gte",
@@ -137,13 +142,10 @@ class AllProductsView(APIView):
)
def get(self, request):
try:
# Get list of category IDs from query parameters
category_ids = request.query_params.getlist('category', [])
if category_ids:
# Convert category IDs to integers and filter products by these categories
category_ids = [int(id) for id in category_ids]
products = ProductModel.objects.filter(category__id__in=category_ids)
category_id = int(request.query_params.get('category', None))
if category_id:
sub_category = get_object_or_404(SubCategoryModel, pk=category_id)
products = ProductModel.objects.filter(category=sub_category)
else:
products = ProductModel.objects.all()
@@ -183,7 +185,7 @@ class AllProductsView(APIView):
serializer = self.serializer_class(paginated_products, many=True)
return paginator.get_paginated_response(serializer.data)
except CategoryModel.DoesNotExist:
except MainCategoryModel.DoesNotExist:
return Response({"detail": "Category not found."}, status=status.HTTP_404_NOT_FOUND)
+19 -3
View File
@@ -19,7 +19,12 @@ services:
volumes:
- ./backend:/app
- media_data:/app/media
command: ["sh", "-c", "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"]
command:
[
"sh",
"-c",
"python manage.py migrate && python manage.py runserver 0.0.0.0:8000",
]
networks:
- default
@@ -36,8 +41,19 @@ services:
networks:
- default
# nginx:
# image: nginx:latest
# volumes:
# - ./nginx.conf:/etc/nginx/nginx.conf
# - /etc/letsencrypt:/etc/letsencrypt
# ports:
# - "80:80"
# - "443:443"
# depends_on:
# - django
# - frontend
# networks:
# - default
volumes:
postgres_data: