categorylanri import qilish qoshildi

This commit is contained in:
Husanjonazamov
2026-03-25 19:24:42 +05:00
commit 1c4155299d
206 changed files with 8106 additions and 0 deletions

View File

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,32 @@
from django.contrib import admin
from unfold.admin import ModelAdmin, TabularInline
from core.apps.api.models import CategoryModel, FilialModel, SubcategoryModel
class SubcategoryInline(TabularInline):
model = SubcategoryModel
extra = 1
@admin.register(FilialModel)
class FilialAdmin(ModelAdmin):
list_display = ("id", "name", "created_at")
search_fields = ("name",)
list_filter = ("created_at",)
@admin.register(CategoryModel)
class CategoryAdmin(ModelAdmin):
list_display = ("id", "name", "filial", "image", "created_at")
list_filter = ("filial", "created_at")
search_fields = ("name",)
list_select_related = ("filial",)
inlines = [SubcategoryInline]
@admin.register(SubcategoryModel)
class SubcategoryAdmin(ModelAdmin):
list_display = ("id", "name", "category", "created_at")
list_filter = ("category", "category__filial", "created_at")
search_fields = ("name", "category__name")
list_select_related = ("category",)

View File

@@ -0,0 +1,25 @@
from django.contrib import admin
from unfold.admin import ModelAdmin, TabularInline
from core.apps.api.models import ProductsModel, SubProductModel
class SubProductInline(TabularInline):
model = SubProductModel
extra = 1
@admin.register(ProductsModel)
class ProductsAdmin(ModelAdmin):
list_display = ("id", "name", "price", "image", "subcategory", "created_at")
list_filter = ("subcategory", "subcategory__category", "subcategory__category__filial", "created_at")
search_fields = ("name", "subcategory__name")
list_select_related = ("subcategory", "subcategory__category")
inlines = [SubProductInline]
@admin.register(SubProductModel)
class SubProductAdmin(ModelAdmin):
list_display = ("id", "name", "product", "price")
list_filter = ("product", "product__subcategory", "created_at")
search_fields = ("name", "product__name")
list_select_related = ("product",)

6
core/apps/api/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ModuleConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "core.apps.api"

View File

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,21 @@
from django_filters import rest_framework as filters
from core.apps.api.models import CategoryModel, SubcategoryModel
class CategoryFilter(filters.FilterSet):
class Meta:
model = CategoryModel
fields = [
"name",
"filial",
]
class SubcategoryFilter(filters.FilterSet):
class Meta:
model = SubcategoryModel
fields = [
"name",
"category",
]

View File

@@ -0,0 +1,17 @@
from django_filters import rest_framework as filters
from core.apps.api.models import ProductsModel
class ProductsFilter(filters.FilterSet):
category = filters.NumberFilter(field_name="subcategory__category_id")
filial = filters.NumberFilter(field_name="subcategory__category__filial_id")
class Meta:
model = ProductsModel
fields = [
"name",
"subcategory",
"category",
"filial",
]

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,17 @@
from django import forms
from core.apps.api.models import CategoryModel, SubcategoryModel
class CategoryForm(forms.ModelForm):
class Meta:
model = CategoryModel
fields = "__all__"
class SubcategoryForm(forms.ModelForm):
class Meta:
model = SubcategoryModel
fields = "__all__"

View File

@@ -0,0 +1,10 @@
from django import forms
from core.apps.api.models import ProductsModel
class ProductsForm(forms.ModelForm):
class Meta:
model = ProductsModel
fields = "__all__"

View File

@@ -0,0 +1,79 @@
# Generated by Django 5.2.7 on 2026-03-25 14:13
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='CategoryModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='name')),
('image', models.ImageField(blank=True, null=True, upload_to='categories/', verbose_name='image')),
],
options={
'verbose_name': 'CategoryModel',
'verbose_name_plural': 'CategoryModels',
'db_table': 'category',
},
),
migrations.CreateModel(
name='SubcategoryModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='name')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subcategories', to='api.categorymodel', verbose_name='category')),
],
options={
'verbose_name': 'SubcategoryModel',
'verbose_name_plural': 'SubcategoryModels',
'db_table': 'subcategory',
},
),
migrations.CreateModel(
name='ProductsModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='name')),
('price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='price')),
('image', models.ImageField(blank=True, null=True, upload_to='products/', verbose_name='image')),
('subcategory', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='api.subcategorymodel', verbose_name='subcategory')),
],
options={
'verbose_name': 'ProductsModel',
'verbose_name_plural': 'ProductsModels',
'db_table': 'products',
},
),
migrations.CreateModel(
name='SubProductModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='name')),
('price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='price')),
('image', models.ImageField(blank=True, null=True, upload_to='subproducts/', verbose_name='image')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subproducts', to='api.productsmodel', verbose_name='product')),
],
options={
'verbose_name': 'SubProductModel',
'verbose_name_plural': 'SubProductModels',
'db_table': 'subproduct',
},
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.2.7 on 2026-03-25 14:17
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='FilialModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, verbose_name='name')),
],
options={
'verbose_name': 'FilialModel',
'verbose_name_plural': 'FilialModels',
'db_table': 'filial',
},
),
migrations.AddField(
model_name='categorymodel',
name='filial',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='categories', to='api.filialmodel', verbose_name='filial'),
),
]

View File

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,67 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_core.models import AbstractBaseModel
from model_bakery import baker
class FilialModel(AbstractBaseModel):
name = models.CharField(verbose_name=_("name"), max_length=255)
def __str__(self):
return self.name
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "filial"
verbose_name = _("FilialModel")
verbose_name_plural = _("FilialModels")
class CategoryModel(AbstractBaseModel):
filial = models.ForeignKey(
FilialModel,
verbose_name=_("filial"),
related_name="categories",
on_delete=models.CASCADE,
null=True,
blank=True,
)
name = models.CharField(verbose_name=_("name"), max_length=255)
image = models.ImageField(verbose_name=_("image"), upload_to="categories/", null=True, blank=True)
def __str__(self):
return self.name
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "category"
verbose_name = _("CategoryModel")
verbose_name_plural = _("CategoryModels")
class SubcategoryModel(AbstractBaseModel):
category = models.ForeignKey(
CategoryModel,
verbose_name=_("category"),
related_name="subcategories",
on_delete=models.CASCADE,
)
name = models.CharField(verbose_name=_("name"), max_length=255)
def __str__(self):
return self.name
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "subcategory"
verbose_name = _("SubcategoryModel")
verbose_name_plural = _("SubcategoryModels")

View File

@@ -0,0 +1,55 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_core.models import AbstractBaseModel
from model_bakery import baker
from core.apps.api.models.category import SubcategoryModel
class ProductsModel(AbstractBaseModel):
name = models.CharField(verbose_name=_("name"), max_length=255)
price = models.DecimalField(verbose_name=_("price"), max_digits=10, decimal_places=2, default=0.0)
image = models.ImageField(verbose_name=_("image"), upload_to="products/", null=True, blank=True)
subcategory = models.ForeignKey(
SubcategoryModel,
verbose_name=_("subcategory"),
related_name="products",
on_delete=models.CASCADE,
)
def __str__(self):
return self.name
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "products"
verbose_name = _("ProductsModel")
verbose_name_plural = _("ProductsModels")
class SubProductModel(AbstractBaseModel):
product = models.ForeignKey(
ProductsModel,
verbose_name=_("product"),
related_name="subproducts",
on_delete=models.CASCADE,
)
name = models.CharField(verbose_name=_("name"), max_length=255)
price = models.DecimalField(verbose_name=_("price"), max_digits=10, decimal_places=2, default=0.0)
image = models.ImageField(verbose_name=_("image"), upload_to="subproducts/", null=True, blank=True)
def __str__(self):
return f"{self.product.name} - {self.name}"
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "subproduct"
verbose_name = _("SubProductModel")
verbose_name_plural = _("SubProductModels")

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,23 @@
from rest_framework import permissions
class CategoryPermission(permissions.BasePermission):
def __init__(self) -> None: ...
def __call__(self, *args, **kwargs):
return self
def has_permission(self, request, view):
return True
class SubcategoryPermission(permissions.BasePermission):
def __init__(self) -> None: ...
def __call__(self, *args, **kwargs):
return self
def has_permission(self, request, view):
return True

View File

@@ -0,0 +1,12 @@
from rest_framework import permissions
class ProductsPermission(permissions.BasePermission):
def __init__(self) -> None: ...
def __call__(self, *args, **kwargs):
return self
def has_permission(self, request, view):
return True

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,3 @@
from .category import * # noqa
from .subcategory import * # noqa
from .filial import * # noqa

View File

@@ -0,0 +1,40 @@
from rest_framework import serializers
from core.apps.api.models import CategoryModel
from core.apps.api.serializers.category.subcategory import BaseSubcategorySerializer
class BaseCategorySerializer(serializers.ModelSerializer):
subcategories = BaseSubcategorySerializer(many=True, read_only=True)
class Meta:
model = CategoryModel
fields = [
"id",
"name",
"image",
"subcategories",
]
class ListCategorySerializer(BaseCategorySerializer):
class Meta(BaseCategorySerializer.Meta):
fields = [
"id",
"name",
"image",
"subcategories",
]
class RetrieveCategorySerializer(BaseCategorySerializer):
class Meta(BaseCategorySerializer.Meta): ...
class CreateCategorySerializer(BaseCategorySerializer):
class Meta(BaseCategorySerializer.Meta):
fields = [
"id",
"name",
"image",
]

View File

@@ -0,0 +1,10 @@
from rest_framework import serializers
from core.apps.api.models import FilialModel
class FilialSerializer(serializers.ModelSerializer):
class Meta:
model = FilialModel
fields = [
"id",
"name",
]

View File

@@ -0,0 +1,39 @@
from rest_framework import serializers
from core.apps.api.models import SubcategoryModel
from core.apps.api.serializers.products.products import ListProductsSerializer
class BaseSubcategorySerializer(serializers.ModelSerializer):
products = ListProductsSerializer(many=True, read_only=True)
class Meta:
model = SubcategoryModel
fields = [
"id",
"category",
"name",
"products",
]
class ListSubcategorySerializer(BaseSubcategorySerializer):
class Meta(BaseSubcategorySerializer.Meta):
fields = [
"id",
"category",
"name",
]
class RetrieveSubcategorySerializer(BaseSubcategorySerializer):
class Meta(BaseSubcategorySerializer.Meta): ...
class CreateSubcategorySerializer(BaseSubcategorySerializer):
class Meta(BaseSubcategorySerializer.Meta):
fields = [
"id",
"category",
"name",
]

View File

@@ -0,0 +1 @@
from .products import * # noqa

View File

@@ -0,0 +1,55 @@
from rest_framework import serializers
from core.apps.api.models import ProductsModel, SubProductModel
class SubProductSerializer(serializers.ModelSerializer):
class Meta:
model = SubProductModel
fields = [
"id",
"name",
"price",
"image",
]
class BaseProductsSerializer(serializers.ModelSerializer):
subproducts = SubProductSerializer(many=True, read_only=True)
class Meta:
model = ProductsModel
fields = [
"id",
"name",
"price",
"image",
"subcategory",
"subproducts",
]
class ListProductsSerializer(BaseProductsSerializer):
class Meta(BaseProductsSerializer.Meta):
fields = [
"id",
"name",
"price",
"image",
"subcategory",
]
class RetrieveProductsSerializer(BaseProductsSerializer):
class Meta(BaseProductsSerializer.Meta): ...
class CreateProductsSerializer(BaseProductsSerializer):
class Meta(BaseProductsSerializer.Meta):
fields = [
"id",
"subcategory",
"name",
"price",
"image",
]

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,12 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from core.apps.api.models import CategoryModel, SubcategoryModel
@receiver(post_save, sender=CategoryModel)
def CategorySignal(sender, instance, created, **kwargs): ...
@receiver(post_save, sender=SubcategoryModel)
def SubcategorySignal(sender, instance, created, **kwargs): ...

View File

@@ -0,0 +1,8 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from core.apps.api.models import ProductsModel
@receiver(post_save, sender=ProductsModel)
def ProductsSignal(sender, instance, created, **kwargs): ...

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,2 @@
from .test_category import * # noqa
from .test_subcategory import * # noqa

View File

@@ -0,0 +1,101 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.api.models import CategoryModel
@pytest.fixture
def instance(db):
return CategoryModel._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("category-list"),
"retrieve": reverse("category-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("category-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
# @pytest.mark.django_db
# def test_create(data):
# urls, client, _ = data
# response = client.post(urls["list"], data={"name": "test"})
# assert response.json()["status"] is True
# assert response.status_code == 201
# @pytest.mark.django_db
# def test_update(data):
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_partial_update():
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_destroy(data):
# urls, client, _ = data
# response = client.delete(urls["retrieve"])
# assert response.status_code == 204

View File

@@ -0,0 +1,101 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.api.models import SubcategoryModel
@pytest.fixture
def instance(db):
return SubcategoryModel._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("subcategory-list"),
"retrieve": reverse("subcategory-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("subcategory-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
# @pytest.mark.django_db
# def test_create(data):
# urls, client, _ = data
# response = client.post(urls["list"], data={"name": "test"})
# assert response.json()["status"] is True
# assert response.status_code == 201
# @pytest.mark.django_db
# def test_update(data):
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_partial_update():
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_destroy(data):
# urls, client, _ = data
# response = client.delete(urls["retrieve"])
# assert response.status_code == 204

View File

@@ -0,0 +1 @@
from .test_products import * # noqa

View File

@@ -0,0 +1,101 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.api.models import ProductsModel
@pytest.fixture
def instance(db):
return ProductsModel._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("products-list"),
"retrieve": reverse("products-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("products-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
# @pytest.mark.django_db
# def test_create(data):
# urls, client, _ = data
# response = client.post(urls["list"], data={"name": "test"})
# assert response.json()["status"] is True
# assert response.status_code == 201
# @pytest.mark.django_db
# def test_update(data):
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_partial_update():
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_destroy(data):
# urls, client, _ = data
# response = client.delete(urls["retrieve"])
# assert response.status_code == 204

View File

@@ -0,0 +1,101 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.api.models import CategoryModel, FilialModel, ProductsModel, SubProductModel, SubcategoryModel
@pytest.fixture
def api_client():
return APIClient()
@pytest.mark.django_db
def test_hierarchical_filtering(api_client):
# 1. Create Filials
f_bar = FilialModel.objects.create(name="Bar")
f_rest = FilialModel.objects.create(name="Restaurant")
# 2. Create Data linked to Filials
cat1 = CategoryModel.objects.create(name="Electronics", filial=f_bar)
cat2 = CategoryModel.objects.create(name="Clothing", filial=f_rest)
sub1 = SubcategoryModel.objects.create(name="Phones", category=cat1)
sub2 = SubcategoryModel.objects.create(name="Laptops", category=cat1)
sub3 = SubcategoryModel.objects.create(name="T-Shirts", category=cat2)
p1 = ProductsModel.objects.create(name="iPhone", subcategory=sub1, price=1000)
p2 = ProductsModel.objects.create(name="MacBook", subcategory=sub2, price=2000)
p3 = ProductsModel.objects.create(name="Nike Tee", subcategory=sub3, price=50)
sp1 = SubProductModel.objects.create(product=p1, name="128GB", price=1000)
sp2 = SubProductModel.objects.create(product=p1, name="256GB", price=1200)
# 3. Test Filial Filtering on Products
url_prod = reverse("products-list")
# Filter by Filial Bar (f_bar)
response = api_client.get(url_prod, {"filial": f_bar.id})
assert response.status_code == 200
data = response.json()["data"]["results"]
assert len(data) == 2
names = [item["name"] for item in data]
assert "iPhone" in names
assert "MacBook" in names
# Filter by Filial Restaurant (f_rest)
response = api_client.get(url_prod, {"filial": f_rest.id})
assert response.status_code == 200
data = response.json()["data"]["results"]
assert len(data) == 1
assert data[0]["name"] == "Nike Tee"
# 4. Test Filial Filtering on Categories
url_cat_list = reverse("category-list")
response = api_client.get(url_cat_list, {"filial": f_bar.id})
assert response.status_code == 200
data = response.json()["data"]["results"]
assert len(data) == 1
assert data[0]["name"] == "Electronics"
# 2. Test Product Listing with Category Filter
url = reverse("products-list")
# Filter by Category Electronics (cat1)
response = api_client.get(url, {"category": cat1.id})
assert response.status_code == 200
data = response.json()["data"]
# Should have iPhone and MacBook in results
assert len(data["results"]) == 2
names = [item["name"] for item in data["results"]]
assert "iPhone" in names
assert "MacBook" in names
# Filter by Subcategory Phones (sub1)
response = api_client.get(url, {"subcategory": sub1.id})
assert response.status_code == 200
data = response.json()["data"]
assert len(data["results"]) == 1
assert data["results"][0]["name"] == "iPhone"
# 3. Test Category Detail for Nested Data
url_cat = reverse("category-detail", kwargs={"pk": cat1.id})
response = api_client.get(url_cat)
assert response.status_code == 200
data = response.json()["data"]
assert data["name"] == "Electronics"
assert len(data["subcategories"]) == 2
# Check if subcategories have products (if RetrieveSubcategorySerializer includes them)
# Wait, in SubcategorySerializer I added 'products' to BaseSubcategorySerializer
# and RetrieveCategory uses BaseSubcategorySerializer.
phones_sub = next(s for s in data["subcategories"] if s["name"] == "Phones")
assert len(phones_sub["products"]) == 1
assert phones_sub["products"][0]["name"] == "iPhone"
# 4. Test Product Detail for Subproducts (variants)
url_prod = reverse("products-detail", kwargs={"pk": p1.id})
response = api_client.get(url_prod)
assert response.status_code == 200
data = response.json()["data"]
assert len(data["subproducts"]) == 2
variant_names = [v["name"] for v in data["subproducts"]]
assert "128GB" in variant_names
assert "256GB" in variant_names

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,13 @@
from modeltranslation.translator import TranslationOptions, register
from core.apps.api.models import CategoryModel, SubcategoryModel
@register(CategoryModel)
class CategoryTranslation(TranslationOptions):
fields = []
@register(SubcategoryModel)
class SubcategoryTranslation(TranslationOptions):
fields = []

View File

@@ -0,0 +1,8 @@
from modeltranslation.translator import TranslationOptions, register
from core.apps.api.models import ProductsModel
@register(ProductsModel)
class ProductsTranslation(TranslationOptions):
fields = []

12
core/apps/api/urls.py Normal file
View File

@@ -0,0 +1,12 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import CategoryView, FilialView, ProductsView, SubProductView, SubcategoryView
router = DefaultRouter()
router.register("filial", FilialView, basename="filial")
router.register("subcategory", SubcategoryView, basename="subcategory")
router.register("category", CategoryView, basename="category")
router.register("products", ProductsView, basename="products")
router.register("subproducts", SubProductView, basename="subproducts")
urlpatterns = [path("", include(router.urls))]

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,15 @@
# from django.core.exceptions import ValidationError
class CategoryValidator:
def __init__(self): ...
def __call__(self):
return True
class SubcategoryValidator:
def __init__(self): ...
def __call__(self):
return True

View File

@@ -0,0 +1,8 @@
# from django.core.exceptions import ValidationError
class ProductsValidator:
def __init__(self): ...
def __call__(self):
return True

View File

@@ -0,0 +1,2 @@
from .category import * # noqa
from .products import * # noqa

View File

@@ -0,0 +1,75 @@
from django_core.mixins import BaseViewSetMixin
from drf_spectacular.utils import extend_schema
from rest_framework.permissions import AllowAny
from rest_framework.viewsets import ReadOnlyModelViewSet
from core.apps.api.filters.category import CategoryFilter, SubcategoryFilter
from core.apps.api.models import CategoryModel, FilialModel, SubProductModel, SubcategoryModel
from core.apps.api.serializers.category import (
CreateCategorySerializer,
CreateSubcategorySerializer,
FilialSerializer,
ListCategorySerializer,
ListSubcategorySerializer,
RetrieveCategorySerializer,
RetrieveSubcategorySerializer,
)
@extend_schema(tags=["filial"])
class FilialView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = FilialModel.objects.all()
serializer_class = FilialSerializer
permission_classes = [AllowAny]
action_permission_classes = {}
action_serializer_class = {
"list": FilialSerializer,
"retrieve": FilialSerializer,
"create": FilialSerializer,
}
from core.apps.api.serializers.products.products import SubProductSerializer
@extend_schema(tags=["category"])
class CategoryView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = CategoryModel.objects.all()
serializer_class = ListCategorySerializer
permission_classes = [AllowAny]
filterset_class = CategoryFilter
action_permission_classes = {}
action_serializer_class = {
"list": ListCategorySerializer,
"retrieve": RetrieveCategorySerializer,
"create": CreateCategorySerializer,
}
@extend_schema(tags=["subcategory"])
class SubcategoryView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = SubcategoryModel.objects.all()
serializer_class = ListSubcategorySerializer
permission_classes = [AllowAny]
filterset_class = SubcategoryFilter
action_permission_classes = {}
action_serializer_class = {
"list": ListSubcategorySerializer,
"retrieve": RetrieveSubcategorySerializer,
"create": CreateSubcategorySerializer,
}
@extend_schema(tags=["subproduct"])
class SubProductView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = SubProductModel.objects.all()
serializer_class = SubProductSerializer
permission_classes = [AllowAny]
action_permission_classes = {}
action_serializer_class = {
"list": SubProductSerializer,
"retrieve": SubProductSerializer,
"create": SubProductSerializer,
}

View File

@@ -0,0 +1,29 @@
from django_core.mixins import BaseViewSetMixin
from drf_spectacular.utils import extend_schema
from rest_framework.permissions import AllowAny
from rest_framework.viewsets import ReadOnlyModelViewSet
from core.apps.api.models import ProductsModel
from core.apps.api.serializers.products import (
CreateProductsSerializer,
ListProductsSerializer,
RetrieveProductsSerializer,
)
from core.apps.api.filters.products import ProductsFilter
@extend_schema(tags=["products"])
class ProductsView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = ProductsModel.objects.all()
serializer_class = ListProductsSerializer
permission_classes = [AllowAny]
filterset_class = ProductsFilter
action_permission_classes = {}
action_serializer_class = {
"list": ListProductsSerializer,
"retrieve": RetrieveProductsSerializer,
"create": CreateProductsSerializer,
}