unit category added
This commit is contained in:
@@ -182,6 +182,12 @@ UNFOLD = {
|
|||||||
"collapsible": True,
|
"collapsible": True,
|
||||||
"items": [
|
"items": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": _("دسته بندی واحد"),
|
||||||
|
"icon": "category",
|
||||||
|
"link": reverse_lazy("admin:product_unitcategorymodel_changelist"),
|
||||||
|
"permission": lambda request: request.user.is_superuser,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": _("دسته بندی"),
|
"title": _("دسته بندی"),
|
||||||
"icon": "category",
|
"icon": "category",
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ class ProductDetailCategoryAdmin(ModelAdmin, ImportExportModelAdmin):
|
|||||||
return request.user.is_superuser
|
return request.user.is_superuser
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(UnitCategoryModel)
|
||||||
|
class UnitCategoryAdmin(ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(InPackItems)
|
@admin.register(InPackItems)
|
||||||
class InPackItemsAdmin(ModelAdmin, ImportExportModelAdmin):
|
class InPackItemsAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||||
import_form_class = ImportForm
|
import_form_class = ImportForm
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-12-04 09:48
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0057_productvariant_profit_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UnitCategoryModel',
|
||||||
|
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.ImageField(blank=True, null=True, upload_to='category_model/', verbose_name='آیکون')),
|
||||||
|
('image', models.ImageField(blank=True, null=True, upload_to='category_model/', 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.RenameIndex(
|
||||||
|
model_name='maincategorymodel',
|
||||||
|
new_name='unit_category_slug_idx',
|
||||||
|
old_name='main_category_slug_idx',
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='unitcategorymodel',
|
||||||
|
index=models.Index(fields=['slug'], name='main_category_slug_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='maincategorymodel',
|
||||||
|
name='parent',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='unitcategorys', to='product.unitcategorymodel', verbose_name='دسته\u200cبندی والد'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-12-04 09:56
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0058_unitcategorymodel_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='maincategorymodel',
|
||||||
|
name='parent',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='maincategorys', to='product.unitcategorymodel', verbose_name='دسته\u200cبندی والد'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -8,6 +8,38 @@ from django.core.exceptions import ValidationError
|
|||||||
from home.models import ShowCaseSlider
|
from home.models import ShowCaseSlider
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class UnitCategoryModel(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)
|
||||||
|
image = 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 = "دستهبندیهااصلی"
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['slug'], name='main_category_slug_idx'),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.slug:
|
||||||
|
self.slug = 'unit-category-' + slugify(self.name, allow_unicode=True)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MainCategoryModel(models.Model):
|
class MainCategoryModel(models.Model):
|
||||||
name = models.CharField(max_length=50, verbose_name='نام دسته بندی')
|
name = models.CharField(max_length=50, verbose_name='نام دسته بندی')
|
||||||
slug = models.SlugField(max_length=50, unique=True,
|
slug = models.SlugField(max_length=50, unique=True,
|
||||||
@@ -22,12 +54,13 @@ class MainCategoryModel(models.Model):
|
|||||||
max_length=160, verbose_name="توضیحات متا", help_text="توضیحات متا برای SEO", blank=True, null=True)
|
max_length=160, verbose_name="توضیحات متا", help_text="توضیحات متا برای SEO", blank=True, null=True)
|
||||||
video = models.FileField(upload_to='category_videos/',
|
video = models.FileField(upload_to='category_videos/',
|
||||||
blank=True, null=True, verbose_name='ویدیو')
|
blank=True, null=True, verbose_name='ویدیو')
|
||||||
|
parent = models.ForeignKey(UnitCategoryModel, on_delete=models.CASCADE,
|
||||||
|
related_name='maincategorys', verbose_name='دستهبندی والد', null=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "دستهبندی اصلی"
|
verbose_name = "دستهبندی اصلی"
|
||||||
verbose_name_plural = "دستهبندیهااصلی"
|
verbose_name_plural = "دستهبندیهااصلی"
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['slug'], name='main_category_slug_idx'),
|
models.Index(fields=['slug'], name='unit_category_slug_idx'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -110,6 +110,13 @@ class SubCategorySerializer(serializers.ModelSerializer):
|
|||||||
return obj.parent.name
|
return obj.parent.name
|
||||||
|
|
||||||
|
|
||||||
|
class UnitCategorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = UnitCategoryModel
|
||||||
|
fields = ['id', 'name', 'slug', 'icon', 'meta_title',
|
||||||
|
'meta_description', 'image']
|
||||||
|
|
||||||
|
|
||||||
class MainCategorySerializer(serializers.ModelSerializer):
|
class MainCategorySerializer(serializers.ModelSerializer):
|
||||||
subcategorys = SubCategorySerializer(many=True)
|
subcategorys = SubCategorySerializer(many=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from .views import AllCategories, ProductView, AllProductsView, CommentView, ShowCaseProductsView, ShowCaseCategoryListView, BotProductsView,BotProductDetailView,BotCategoryView
|
from .views import AllCategories, ProductView, AllProductsView, CommentView, ShowCaseProductsView, ShowCaseCategoryListView, BotProductsView,BotProductDetailView,BotCategoryView ,AllCategoriesV2
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('slider_category', ShowCaseProductsView.as_view(), name='category-products'),
|
path('slider_category', ShowCaseProductsView.as_view(), name='category-products'),
|
||||||
path('bot', BotProductsView.as_view(), name='bot-products'),
|
path('bot', BotProductsView.as_view(), name='bot-products'),
|
||||||
path('bot/<int:pk>/', BotProductDetailView.as_view(), name='bot-product-detail'),
|
path('bot/<int:pk>/', BotProductDetailView.as_view(), name='bot-product-detail'),
|
||||||
path('categories', AllCategories.as_view(), name='all-categories'),
|
path('categories', AllCategories.as_view(), name='all-categories'),
|
||||||
|
path('categories/v2', AllCategoriesV2.as_view(), name='all-categories'),
|
||||||
path('categories/bot', BotCategoryView.as_view(), name='bot-categories'),
|
path('categories/bot', BotCategoryView.as_view(), name='bot-categories'),
|
||||||
path('slider_categories', ShowCaseCategoryListView.as_view(), name='all-categories'),
|
path('slider_categories', ShowCaseCategoryListView.as_view(), name='all-categories'),
|
||||||
re_path(r'^comments/(?P<slug>[\w\u0600-\u06FF\-]+)$', CommentView.as_view(), name='comment-views'),
|
re_path(r'^comments/(?P<slug>[\w\u0600-\u06FF\-]+)$', CommentView.as_view(), name='comment-views'),
|
||||||
|
|||||||
@@ -65,6 +65,48 @@ class AllCategories(APIView):
|
|||||||
categories_ser = self.serializer_class(
|
categories_ser = self.serializer_class(
|
||||||
instance=categories, many=True, context={'request': request})
|
instance=categories, many=True, context={'request': request})
|
||||||
return Response(categories_ser.data, status=status.HTTP_200_OK)
|
return Response(categories_ser.data, status=status.HTTP_200_OK)
|
||||||
|
class UnitCategorySerializerV2(serializers.ModelSerializer):
|
||||||
|
maincategorys = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UnitCategoryModel
|
||||||
|
fields = ['id', 'name', 'slug', 'icon', 'meta_title',
|
||||||
|
'meta_description', 'image', 'maincategorys']
|
||||||
|
|
||||||
|
def get_maincategorys(self, obj):
|
||||||
|
main_categories = obj.maincategorys.all()
|
||||||
|
return MainCategorySerializer(main_categories, many=True, context=self.context).data
|
||||||
|
|
||||||
|
|
||||||
|
class AllCategoriesV2(APIView):
|
||||||
|
serializer_class = UnitCategorySerializerV2
|
||||||
|
authentication_classes = []
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
responses={
|
||||||
|
200: UnitCategorySerializerV2(many=True),
|
||||||
|
404: OpenApiTypes.OBJECT,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
# Optimize query with prefetch_related to avoid N+1 queries
|
||||||
|
unit_categories = UnitCategoryModel.objects.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
'maincategorys',
|
||||||
|
queryset=MainCategoryModel.objects.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
'subcategorys',
|
||||||
|
queryset=SubCategoryModel.objects.annotate(
|
||||||
|
product_count=Count('products')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
categories_ser = self.serializer_class(
|
||||||
|
instance=unit_categories, many=True, context={'request': request})
|
||||||
|
return Response(categories_ser.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class ProductView(APIView):
|
class ProductView(APIView):
|
||||||
|
|||||||
Reference in New Issue
Block a user