db va type togirlandi

This commit is contained in:
khego
2026-03-31 20:46:54 +05:00
parent cf23a61b98
commit 2dee1cc1e6
39 changed files with 5733 additions and 21 deletions

7
.claude/settings.json Normal file
View File

@@ -0,0 +1,7 @@
{
"permissions": {
"additionalDirectories": [
"/Users/uzmacbook/felix/karomat-backend/core/apps/api/filters"
]
}
}

Submodule .claude/worktrees/cool-bohr added at cf23a61b98

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,11 @@ PAGES = [
"icon": "store",
"link": reverse_lazy("admin:api_filialmodel_changelist"),
},
{
"title": _("Turlar"),
"icon": "label",
"link": reverse_lazy("admin:api_typemodel_changelist"),
},
{
"title": _("Kategoriyalar"),
"icon": "category",

View File

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

View File

@@ -0,0 +1,8 @@
from core.apps.api.models import TypeModel
from unfold.admin import ModelAdmin
from django.contrib import admin
@admin.register(TypeModel)
class TypeAdmin(ModelAdmin):
list_display = ("id", "__str__",)

View File

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

View File

@@ -4,6 +4,8 @@ from core.apps.api.models import CategoryModel, SubcategoryModel
class CategoryFilter(filters.FilterSet):
type = filters.NumberFilter(field_name="type_id")
class Meta:
model = CategoryModel
fields = [

View File

@@ -6,7 +6,7 @@ from core.apps.api.models import ProductsModel, SubProductModel
class ProductsFilter(filters.FilterSet):
category = filters.NumberFilter(field_name="subcategory__category_id")
filial = filters.NumberFilter(field_name="subcategory__category__filial_id")
category_type = filters.CharFilter(field_name="subcategory__category__type")
category_type = filters.NumberFilter(field_name="subcategory__category__type_id")
class Meta:
model = ProductsModel
@@ -20,7 +20,7 @@ class ProductsFilter(filters.FilterSet):
class SubProductFilter(filters.FilterSet):
category_type = filters.CharFilter(field_name="product__subcategory__category__type")
category_type = filters.NumberFilter(field_name="product__subcategory__category__type_id")
class Meta:
model = SubProductModel

View File

@@ -0,0 +1,8 @@
from core.apps.api.models import TypeModel
from django_filters import rest_framework as filters
class TypeFilter(filters.FilterSet):
class Meta:
model = TypeModel
fields = ["name"]

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
"""
1-qadam: TypeModel jadvalini yaratish va categoryga type_fk ustunini qo'shish.
Eski 'type' (CharField) ustuni hali o'chmaydi — ma'lumot saqlanadi.
"""
dependencies = [
('api', '0008_subproductmodel_description'),
]
operations = [
migrations.CreateModel(
name='TypeModel',
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': 'TypeModel',
'verbose_name_plural': 'TypeModels',
'db_table': 'type',
},
),
# Yangi FK ustun qo'shamiz (type_fk_id), eski type ustuni saqlanib qoladi
migrations.AddField(
model_name='categorymodel',
name='type_fk',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='categories',
to='api.typemodel',
verbose_name='type',
),
),
]

View File

@@ -0,0 +1,48 @@
from django.db import migrations
def forwards(apps, schema_editor):
"""
Eski type (CharField: RESTAURANT/BAR) qiymatlaridan TypeModel yozuvlari yaratadi,
keyin har bir categoryni to'g'ri TypeModelga ulaydi.
"""
TypeModel = apps.get_model('api', 'TypeModel')
CategoryModel = apps.get_model('api', 'CategoryModel')
# DB dagi barcha unique type qiymatlarini topib TypeModel yozuvlari yaratamiz
existing_types = (
CategoryModel.objects.exclude(type__isnull=True)
.exclude(type='')
.values_list('type', flat=True)
.distinct()
)
type_map = {}
for type_value in existing_types:
obj, _ = TypeModel.objects.get_or_create(name=type_value)
type_map[type_value] = obj
# Har bir categoryni mos TypeModelga ulaymiz
for category in CategoryModel.objects.exclude(type__isnull=True).exclude(type=''):
category.type_fk = type_map.get(category.type)
category.save(update_fields=['type_fk'])
def backwards(apps, schema_editor):
CategoryModel = apps.get_model('api', 'CategoryModel')
CategoryModel.objects.update(type_fk=None)
class Migration(migrations.Migration):
"""
2-qadam: Mavjud type (RESTAURANT/BAR) qiymatlaridan TypeModel yozuvlari yaratib,
categorylarni ulaydi.
"""
dependencies = [
('api', '0009_typemodel_category_type_fk'),
]
operations = [
migrations.RunPython(forwards, backwards),
]

View File

@@ -0,0 +1,26 @@
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
"""
3-qadam: Eski type (CharField) ustunini o'chirib, type_fk ni type deb nomini o'zgartirish.
"""
dependencies = [
('api', '0010_migrate_type_data'),
]
operations = [
# Eski CharField type ustunini o'chiramiz
migrations.RemoveField(
model_name='categorymodel',
name='type',
),
# type_fk ni type deb nomini o'zgartirамiz
migrations.RenameField(
model_name='categorymodel',
old_name='type_fk',
new_name='type',
),
]

View File

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

View File

@@ -22,10 +22,6 @@ class FilialModel(AbstractBaseModel):
class CategoryModel(AbstractBaseModel):
class CategoryType(models.TextChoices):
RESTAURANT = "RESTAURANT", _("Restaurant")
BAR = "BAR", _("Bar")
filial = models.ForeignKey(
FilialModel,
verbose_name=_("filial"),
@@ -35,11 +31,13 @@ class CategoryModel(AbstractBaseModel):
blank=True,
)
name = models.CharField(verbose_name=_("name"), max_length=255)
type = models.CharField(
type = models.ForeignKey(
"api.TypeModel",
verbose_name=_("type"),
max_length=20,
choices=CategoryType.choices,
default=CategoryType.RESTAURANT,
related_name="categories",
on_delete=models.SET_NULL,
null=True,
blank=True,
)
image = models.ImageField(verbose_name=_("image"), upload_to="categories/", null=True, blank=True)
active_image = models.ImageField(
@@ -49,7 +47,8 @@ class CategoryModel(AbstractBaseModel):
def __str__(self):
filial_name = self.filial.name if self.filial else "Filial yo'q"
return f"{self.name} ({self.type}) - {filial_name}"
type_name = self.type.name if self.type else "Type yo'q"
return f"{self.name} ({type_name}) - {filial_name}"
@classmethod
def _baker(cls):

View File

@@ -0,0 +1,20 @@
from django.db import models
from django_core.models import AbstractBaseModel
from django.utils.translation import gettext_lazy as _
from model_bakery import baker
class TypeModel(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 = "type"
verbose_name = _("TypeModel")
verbose_name_plural = _("TypeModels")

View File

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

View File

@@ -0,0 +1,7 @@
from rest_framework import permissions
class TypePermission(permissions.BasePermission):
def has_permission(self, request, view):
return True

View File

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

View File

@@ -2,10 +2,19 @@ from rest_framework import serializers
from core.apps.api.models import CategoryModel
from core.apps.api.serializers.category.subcategory import BaseSubcategorySerializer
from core.apps.api.serializers.type.type import BaseTypeSerializer
class BaseCategorySerializer(serializers.ModelSerializer):
subcategories = BaseSubcategorySerializer(many=True, read_only=True)
type = BaseTypeSerializer(read_only=True)
type_id = serializers.PrimaryKeyRelatedField(
source="type",
queryset=CategoryModel._meta.get_field("type").related_model.objects.all(),
write_only=True,
required=False,
allow_null=True,
)
class Meta:
model = CategoryModel
@@ -13,6 +22,7 @@ class BaseCategorySerializer(serializers.ModelSerializer):
"id",
"name",
"type",
"type_id",
"order",
"image",
"active_image",
@@ -42,6 +52,7 @@ class CreateCategorySerializer(BaseCategorySerializer):
fields = [
"id",
"name",
"type_id",
"order",
"image",
"active_image",

View File

@@ -4,7 +4,7 @@ from core.apps.api.models import ProductsModel, SubProductModel
class SubProductSerializer(serializers.ModelSerializer):
type = serializers.CharField(source="product.subcategory.category.type", read_only=True)
type = serializers.CharField(source="product.subcategory.category.type.name", read_only=True)
class Meta:
model = SubProductModel
@@ -20,7 +20,7 @@ class SubProductSerializer(serializers.ModelSerializer):
class BaseProductsSerializer(serializers.ModelSerializer):
subproducts = SubProductSerializer(many=True, read_only=True)
type = serializers.CharField(source="subcategory.category.type", read_only=True)
type = serializers.CharField(source="subcategory.category.type.name", read_only=True)
class Meta:
model = ProductsModel

View File

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

View File

@@ -0,0 +1,31 @@
from core.apps.api.models import Type Model
from rest_framework import serializers
class BaseType Serializer(serializers.ModelSerializer):
class Meta:
model = Type Model
fields = [
"id",
"name",
]
class ListType Serializer(BaseType Serializer):
class Meta(BaseType Serializer.Meta): ...
class RetrieveType Serializer(BaseType Serializer):
class Meta(BaseType Serializer.Meta): ...
class CreateType Serializer(BaseType Serializer):
class Meta(BaseType Serializer.Meta):
fields = [
"id",
"name",
]

View File

@@ -0,0 +1,21 @@
from core.apps.api.models import TypeModel
from rest_framework import serializers
class BaseTypeSerializer(serializers.ModelSerializer):
class Meta:
model = TypeModel
fields = ["id", "name"]
class ListTypeSerializer(BaseTypeSerializer):
class Meta(BaseTypeSerializer.Meta): ...
class RetrieveTypeSerializer(BaseTypeSerializer):
class Meta(BaseTypeSerializer.Meta): ...
class CreateTypeSerializer(BaseTypeSerializer):
class Meta(BaseTypeSerializer.Meta):
fields = ["id", "name"]

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.api.models import TypeModel
@pytest.fixture
def instance(db):
return TypeModel._baker()
@pytest.fixture
def api_client(instance):
client = APIClient()
return client, instance
@pytest.fixture
def data(api_client):
client, instance = api_client
return (
{
"list": reverse("type-list"),
"retrieve": reverse("type-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("type-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

View File

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

View File

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

View File

@@ -1,12 +1,18 @@
from django.urls import include, path
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import CategoryView, FilialView, ProductsView, SubProductView, SubcategoryView
from core.apps.api.views.category import FilialView, CategoryView, SubcategoryView, SubProductView
from core.apps.api.views.products import ProductsView
from core.apps.api.views.type import TypeView
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))]
router.register("filials", FilialView, basename="filial")
router.register("categories", CategoryView, basename="category")
router.register("subcategories", SubcategoryView, basename="subcategory")
router.register("products", ProductsView, basename="product")
router.register("subproducts", SubProductView, basename="subproduct")
router.register("types", TypeView, basename="type")
urlpatterns = [
path("", include(router.urls)),
]

View File

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

View File

@@ -0,0 +1,6 @@
class TypeValidator:
def __init__(self):
...
def __call__(self):
return True

View File

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

View File

@@ -0,0 +1,20 @@
from core.apps.api.models import TypeModel
from core.apps.api.serializers.type import RetrieveTypeSerializer, ListTypeSerializer, CreateTypeSerializer
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
@extend_schema(tags=["type"])
class TypeView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = TypeModel.objects.all()
serializer_class = ListTypeSerializer
permission_classes = [AllowAny]
action_permission_classes = {}
action_serializer_class = {
"list": ListTypeSerializer,
"retrieve": RetrieveTypeSerializer,
"create": CreateTypeSerializer,
}

File diff suppressed because it is too large Load Diff